Map first element of stream differently than rest - java-8

Is there a way in Java's Stream API to map first element of stream differently than other?
Equivalent of this code:
List<Bar> barList = new ArrayList<>();
for (int i=0; i<fooList.size(); i++) {
Foo foo = fooList.get(i);
Foo modifiedFoo = foo.getModifiedFoo();
if (i == 0) {
barList.add(new Bar(modifiedFoo, false));
}else {
barList.add(new Bar(modifiedFoo, true));
}
}
Stream<Bar> = barList.stream();
Note: I already have a stream setup and I would want some operation after first mapping
fooList.stream()
.map(Foo::getModifiedFoo)
.(May be Some operation here to get different new Bar for first modifiedFoo)
.map(modifiedFoo -> new Bar(modifiedFoo, true));

I would get the first element, create a Stream out of it and apply the needed mappings. Then, I'd take the rest of the list, create a stream out of it and apply the different mappings. Then concat the streams. Something like this:
Stream<Bar> first = Stream.of(fooList.get(0))
.map(Foo::getModifiedFoo)
.map(modifiedFoo -> new Bar(modifiedFoo, false));
Stream<Bar> others = fooList.subList(1, fooList.size()).stream()
.map(Foo::getModifiedFoo)
.map(modifiedFoo -> new Bar(modifiedFoo, true));
Stream<Bar> bars = Stream.concat(first, others).flatMap(s -> s);
Another approach:
Stream<Bar> bars = IntStream.range(0, fooList.size())
.mapToObj(i -> new Bar(fooList.get(i).getModifiedFoo(), i > 0));
This way is succinct and does the job pretty well.

Use an IntStream to iterate over the indices, then mapToObj to create an object for that index, and finally collect into a list:
List<Bar> barList = IntStream.range(0, fooList.size())
.mapToObj(i -> (i == 0 ? new Bar (fooList.get(i), false) :
new Bar(fooList.get(i),true)))
.collect(Collectors.toList());
What would be more readable though, is doing the first item handling outside the loop, and using IntStream starting with 1.
Here is a demo using simple lists.

I can propose two ways but I find your way straighter.
With IntStream such as :
List<Bar> barList = new ArrayList<>();
IntStream.range(0, fooList.size())
.forEach(i->{
if (i == 0) {
barList.add(new Bar(foo, false));
}else {
barList.add(new Bar(foo, true));
}
}
);
It is not a real functional approach (forEach() use and no Collector) because it maintains the current index of the List.
As alternative, you could use a more functional approach but I don't find it straighter either :
List<Bar> barList = IntStream.range(0, fooList.size())
.mapToObj(i->{
Foo foo = fooList.get(i);
if (i == 0) {
return new Bar(foo, false);
}
return new Bar(foo, true));
})
.collect(Collectors.toList());

Although I think the accepted answer is better, here is an alternate approach.
int[] counter = {-1};
Stream<Bar> barListStream = fooList.stream().map(foo -> {
counter[0]++;
return new Bar(mfoo.getModifiedFoo(), counter[0]>0);
}).collect(Collectors.toList()).stream();

You can have an object to hold a flag e.g. AtomicBoolean or AtomicInteger - that you can reset on first or nth iteration (you would need something like AtomicInteger or some Integer holder to reset on nth iteration) e.g. following code using HashMap as the holder class - will print first line of the stream differently than the other lines:
Map<String, Boolean> firstTime = new HashMap<>(Map.of("firstTime", true)); // to make the map modifiable
try (Stream<String> lines = Files.lines(Paths.get(filename), Charset.defaultCharset())) {
lines.forEachOrdered(line -> System.out.println(firstTime.remove("firstTime") != null ? ("firstTime: " + line) : line));
}

Use an AtomicBoolean initially set to true to determine when is the first item.
final AtomicBoolean first = new AtomicBoolean(true);
System.out.println("** Print all numbers 1..10");
IntStream.range(1, 11).forEach(number -> {
System.out.print((first.get() ? "" : ",") + number);
first.set(false);
});
System.out.println();

Related

Collect groupBy on deep property

private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
Map<String, Set<Square>> res = new HashMap<>();
squares.stream()
.filter(square -> {
if (square.getZuloCodes().isEmpty()) {
LOG("Ignored {}", square.id);
return false;
}
return true;
})
.forEach(square -> {
square.getZuloCodes()
.forEach(code -> {
res.putIfAbsent(code, new HashSet<>());
res.get(code).add(square);
}));
});
return Collections.unmodifiableMap(res);
}
The code above receives a list of Squares, and those squares may contain ZuloCodes inside. The output should be a immutable Map zuloCode and value all the squares with that UniquePrefix.
As you can see I cannot figure out a way to remove the auxiliar collection res and make the code easily readable, is there a way to explode that collection into a [zuloCode, square] and then collect.groupBy ? Also that if inside the filter is so unreadable, how would you tackle it?
The standard approach is using flatMap before collecting using groupingBy, but since you need the original Square for each element, you need to map to an object holding both, the Square instance and the zulo code String.
Since there is no standard pair or tuple type in Java (yet), a work-around is to use a Map.Entry instance, like this
private Map<String, Set<Square>> populateZuloSquare0(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
return squares.stream()
.filter(square -> logMismatch(square, !square.getZuloCodes().isEmpty()))
.flatMap(square -> square.getZuloCodes().stream()
.map(code -> new AbstractMap.SimpleEntry<>(code, square)))
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toSet())),
Collections::unmodifiableMap));
}
private static boolean logMismatch(Square square, boolean match) {
if(!match) LOG("Ignored {}", square.id);
return match;
}
An alternative is to use a custom collector which will iterate over the keys:
private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
return squares.stream()
.filter(square -> logMismatch(square, !square.getZuloCodes().isEmpty()))
.collect(Collector.of(
HashMap<String, Set<Square>>::new,
(m,square) -> square.getZuloCodes()
.forEach(code -> m.computeIfAbsent(code, x -> new HashSet<>()).add(square)),
(m1,m2) -> {
if(m1.isEmpty()) return m2;
m2.forEach((key,set) ->
m1.merge(key, set, (s1,s2) -> { s1.addAll(s2); return s1; }));
return m1;
},
Collections::unmodifiableMap)
);
}
Note that this custom collector can be seen as a parallel capable variant of the following looping code:
private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
Map<String, Set<Square>> res = new HashMap<>();
squares.forEach(square -> {
if(square.getZuloCodes().isEmpty()) LOG("Ignored {}", square.id);
else square.getZuloCodes().forEach(
code -> res.computeIfAbsent(code, x -> new HashSet<>()).add(square));
});
return Collections.unmodifiableMap(res);
}
which might not look so bad now, when you don’t need the code to be parallel capable…
How about this. You may use map merge operation to get this thing done. I have updated the filter and simplified it too.
squares.stream().filter(s -> !s.getZuloCodes().isEmpty())
.forEach(s -> s.getZuloCodes().stream().forEach(z -> res.merge(z, new HashSet<>(Arrays.asList(s)),
(s1, s2) -> Stream.of(s1, s2).flatMap(Collection::stream).collect(Collectors.toSet()))));

The method forEach(Consumer<? super Map<String,Object>>) in the type Iterable<Map<String,Object>> is not applicable for the arguments ((<no type> k

Using Java 8 and getting the below error. I have a List<Map<String, Object>>, Map<String, Object> contains ACC_NO and true or ACC_NO and false.
Have the Map like this:
[{14125=false}, {1210=false}, {13118=false}, {10607=false}, {4973=false}, {12175=false}..............,
Multiple markers at this line
- Lambda expression's signature does not match the signature of the functional interface method accept(Map)
- The method forEach(Consumer>) in the type Iterable> is not applicable for the arguments (( k,
v) -> {})
I need to simply create another map out of it to used
// If value is false, I need to add that again
List<Map<String, Object>> excludeAccounts = new ArrayList<>();
detailsOfPans.forEach((k, v) -> {
System.out.println("Item : " + k + " Count : " + v);
Map<String, Object> myMap = new HashMap<>();
if(!v) {
excludePANs.add(myMap.put(k, v));
}
});
You are currently only iterating over the outer list instead of the maps which form the items of the list. To solve this, you would use
detailsOfPans.forEach(map -> map.forEach((k, v) -> {
// Do something with k, v
}));

How to collect map from the Set of objects that has a list using Collectors.toMap

I have class Element with a list, my intended output is like this:
Map<String , List<Element>>
{
1 = [Element3, Element1],
2 = [Element2, Element1],
3 = [Element2, Element1], 4=[Element2]
}
And my input is set of element objects, I used forEach to get the desired outcome, but I'm looking for how to collect it using collectors.toMap. Any inputs are much appreciated
Set<Element> changes = new HashSet();
List<String> interesetList = new ArrayList();
interesetList.add("1");
interesetList.add("2");
interesetList.add("3");
Element element = new Element(interesetList);
changes.add(element);
interesetList = new ArrayList();
interesetList.add("2");
interesetList.add("3");
interesetList.add("4");
element = new Element(interesetList);
changes.add(element);
Map<String, List<Element>> collect2 = new HashMap();
changes.forEach(element -> {
element.getInterestedList().forEach(tracker -> {
collect2.compute(tracker, ( key , val) -> {
List<Element> elementList = val == null ? new ArrayList<Element>() : val;
elementList.add(Element);
return elementList;
});
});
});
class Element {
List<String> interestedList;
static AtomicInteger sequencer = new AtomicInteger(0);
String mName;
public Element(List<String> aList) {
interestedList = aList;
mName = "Element" + sequencer.incrementAndGet();
}
public List<String> getInterestedList() {
return interestedList;
}
#Override
public String toString() {
return mName;
}
}
You can do it by using Collectors.groupingBy instead of Collectors.toMap, along with Collectors.mapping, which adapts a collector to another collector:
Map<String, List<Element>> result = changes.stream()
.flatMap(e -> e.getInterestedList().stream().map(t -> Map.entry(t, e)))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
You need to use the Stream.flatMap method first and then pair the elements of the inner lists with the current Element instance. I did this via the new Java 9's Map.entry(key, value) method. If you're not on Java 9 yet, you could change it to new AbstractMap.SimpleEntry<>(key, value).
After flatmapping, we need to collect instances of Map.Entry. So I'm using Collectors.groupingBy to classify entries by key (where we had previously stored each element of the inner lists, aka what you call tracker in your code). Then, as we don't want to have instances of List<Map.Entry<String, Element>> as the values of the map, we need to transform each Map.Entry<String, Element> of the stream to just Element (that's why I'm using Map.Entry::getValue as the first argument of Collectors.mapping). We also need to specify a downstream collector (here Collectors.toList()), so that the outer Collectors.groupingBy collector knows where to place all the adapted elements of the stream that belong to each group.
A shorter and surely more efficient way to do the same (similar to your attempt) could be:
Map<String, List<Element>> result = new HashMap<>();
changes.forEach(e ->
e.getInterestedList().forEach(t ->
result.computeIfAbsent(t, k -> new ArrayList<>()).add(e)));
This uses Map.computeIfAbsent, which is a perfect fit for your use case.

Buffer with other publisher - Spring reactor

Can anyone give an example of how Flux.buffer(Publisher other) works, im unable to make use of the other publisher to split the original flux into multiple lists.
Ex:
List<String> strings = new ArrayList<>();
strings.add("A");
strings.add("B");
Flux<String> stringFlux = Flux.fromIterable(strings).cache();
for(int i = 0; i < 100; i++) {
strings.add(""+i);
}
List<Integer> integers = new ArrayList<>(2);
integers.add(1);
integers.add(1);
integers.add(1);
stringFlux.buffer((a) -> {
Flux.fromIterable(integers);
}).subscribe(a -> {
System.out.println(a);
});
this still prints the original list as the output rather than split it.
bufferuntil with a predicate is actually what i was looking for.

How can I write the following method in Java 8 streams?

How can I write the following method in Java 8 streams? I couldn't find a way to do it. This is my code:
public static List<ObjectB> getFilteredList(List<ObjectA> list, LocalTime startTime, LocalTime endTime, int quantity) {
List<ObjectA> objectAList = new LinkedList<>();
List<ObjectB> objectBList = new LinkedList<>();
for (ObjectA object : list) {
if (object.getDateTime().toLocalTime().isAfter(startTime) && object.getDateTime().toLocalTime().isBefore(endTime)) {
objectAList.add(object);
}
}
for (ObjectA objectA : objectAList) {
int total = 0;
for (ObjectA object : list) {
if (object.getDateTime().toLocalDate().equals(objectA.getDateTime().toLocalDate())) {
total += object.getQuantity();
}
}
if (total > quantity) {
objectBList.add(new ObjectB(objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(), true));
} else {
objectBList.add(new ObjectB(objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(), false));
}
}
return objectBList;}
I have a list of objects with two fields: date and quantity. I need to return a list with one object for each date, but with one more feild - boolean, which should be true if the total sum of all quantites per day is more than 16, and false if it's not.
Let's do this step-by-step.
for (ObjectA object : list) {: a for loop is usually replaced with stream(), so start with list.stream().
if (...) {: condition is usually replaced with filter(), so continue with .filter(object -> object.getDateTime()...)
objectAList.add(object);: adding the results to the container is usually replaced with collect(). You are using LinkedList(), but any other List would be fine here, so we will simply use collect(Collectors.toList()).
So here's first loop:
List<ObjectA> objectAList = list.stream()
.filter(object -> object.getDateTime().toLocalTime().isAfter(startTime) &&
object.getDateTime().toLocalTime().isBefore(endTime))
.collect(Collectors.toList());
Now let's look into the inner loop which calculates the total:
int total = 0;
for (ObjectA object : list) {
if (object.getDateTime().toLocalDate().equals(objectA.getDateTime().toLocalDate())) {
total += object.getQuantity();
}
}
It's also stream-filter-collect sequence, but here you want to collect the sum. So you may use IntStream here which already has the sum() method:
int total = list.stream()
.filter(object -> object.getDateTime().toLocalDate().equals(
objectA.getDateTime().toLocalDate())
.mapToInt(ObjectA::getQuantity).sum();
To make your code less crowded I would extract this to the separate method:
private static int getQuantityByDate(List<ObjectA> list, LocalDate date) {
return list.stream().filter(object -> object.getDateTime().toLocalDate().equals(date))
.mapToInt(ObjectA::getQuantity).sum();
}
Now the next if statement. It just changes the last boolean argument, so I would rewrite it (even without Stream API):
objectBList.add(new ObjectB(objectA.getDateTime(), objectA.getDescription(),
objectA.getQuantity(), total > quantity));
So now we see that the outer loop becomes stream-map-collect chain and could be rewritten this way:
List<ObjectB> objectBList = objectAList.stream()
.map(objectA ->
new ObjectB(objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(),
getQuantityByDate(list, objectA.getDateTime().toLocalDate()) > quantity))
.collect(Collectors.toList());
Now you can notice that collecting into objectAList is unnecessary as we just use it to create another stream. So we can merge both loops into single pipeline, resulting in the following final code:
private static int getQuantityByDate(List<ObjectA> list, LocalDate date) {
return list.stream().filter(object -> object.getDateTime().toLocalDate().equals(date))
.mapToInt(ObjectA::getQuantity).sum();
}
public static List<ObjectB> getFilteredList(
List<ObjectA> list, LocalTime startTime, LocalTime endTime, int quantity) {
return list.stream()
.filter(object -> object.getDateTime().toLocalTime().isAfter(startTime) &&
object.getDateTime().toLocalTime().isBefore(endTime))
.map(objectA -> new ObjectB(
objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(),
getQuantityByDate(list, objectA.getDateTime().toLocalDate()) > quantity))
.collect(Collectors.toList());
}

Resources