terminal stream operation, which takes map as an input in Java 8 - java

Do we have some terminal function which takes a function as an input? I have an inner loop, which returns a response, as put in the below example. Rather than putting the response in outside list, I wanted inner loop to collect and send the response.
List<A> consignments = Lists.emptyList();
IntStream.range(0, days)
.mapToObj(startDate::plusDays)
.forEach(day -> IntStream.range(0, hours)
.forEach(hour -> consignments.add(myfunction()))
);
I wanted it something like:
List<A> consignments = IntStream.range(0, days)
.mapToObj(startDate::plusDays)
.forEach(day -> IntStream.range(0, hours)
.mapToObj(hour -> myFunction())
.collect(toList()));
Since forEach is a consumer, this won't work as it is, so do we have some easy way to achieve it, which I am missing?

If I understand your intention correctly, flatMap should work:
List<A> consignments =
IntStream.range(0, days)
.mapToObj(startDate::plusDays)
.flatMap(day -> IntStream.range(0, hours)
.mapToObj(hour -> myFunction()))
.collect(toList());

Related

Java stream: Collect Stream<int[]> to List<int[]>

I try to get my head around java streams.
Atm I have this construct which does not work:
List<int[]> whiteLists = processes.stream()
.map(Process::getEventList)
.forEach(eventList -> eventList.stream()
.map(event -> event.getPropertie("whitelist"))
.map(propertie -> propertie.getIntArray())
.collect(Collectors.toList()));
}
The hierarchy is:
Process
Event
Property
Process::getEventList returns a list of Event objects
event.getPropertie("whitelist") returns a Property objects which hast the method getIntArray()
event.getPropertie() gives me an int-array.
How do I collect these array into a List of arrays?
Thanks!
You can't use forEach() as it takes a Consumer, meaning it will consume the stream, but can't return anything (so nothing to collect).
You need flatMap to stream the internal eventList as follows
List<int[]> whiteLists = processes.stream()
.flatMap(p -> p.getEventList().stream())
.map(event -> event.getPropertie("whitelist"))
.map(propertie -> propertie.getIntArray())
.collect(Collectors.toList());

Java Streams API: how to perform an operation inside the stream on the whole list result?

Is there a way to use the stream result (List<Integer> in my case) to perform an operation inside the function on that list (all at once)
So instead of this:
var cardTypesToRemove = existingIds.stream()
.filter(c -> !cardTypeIds.contains(c))
.collect(Collectors.toList());
repository.deleteBy(cardTypesToRemove);
Something like this (excuse me for pseudo code)
var cardTypesToRemove = existingIds.stream()
.filter(c -> !cardTypeIds.contains(c))
.collect(Collectors.collectingAndThen(repository.saveAll(resultList)));
No. Just pass the result of the collect as a regular method parameter.
var cardTypesToRemove = repository.saveAll(
existingIds.stream()
.filter(c -> !cardTypeIds.contains(c))
.collect(Collectors.toList());

xml class object get child nodes count using (lamba-> or other optimized) iteration java

I have a soa xml java object, in which I need to get the total count of SItemDetails which is 4 here:
<ns57:PProductsResponse xmlns:ns57="">
<ResponseInfo xmlns="" TransactionId="test"/>
<ns57:PProductsSuccess>
<ONumber xmlns="" ONumber="7">
<OItemNumber>
<PItemDetails Price="0.00" >
<SItemDetails FIdRef="01-01" SId="12D"/>
<SItemDetails FIdRef="01-02" SId="10F"/>
</PItemDetails>
</OItemNumber>
</ONumber>
</ns57:PProductsSuccess>
<ns57:PProductsSuccess>
<ONumber xmlns="" ONumber="7">
<OItemNumber>
<PItemDetails Price="0.00">
<SItemDetails FIdRef="01-02" SId="10G"/>
<SItemDetails FIdRef="01-01" SId="12E"/>
</PItemDetails>
</OItemNumber>
</ONumber>
</ns57:PProductsSuccess>
</ns57:PProductsResponse>
PProductsSuccessType[] pProductSuccess = pProductsResponse.getPProductsResponse().getPProductsSuccess();
long sItemDetailsCount1 = Arrays.stream(pProductSuccess).filter(PProductsSuccessType
-> (PProductsSuccessType.getONumber().getOItemNumber()[0].getPItemDetails().getSItemDetails()!=null)).count();
OR
long sItemDetailsCount2 = Arrays.stream(pProductSuccess)
.flatMap(p -> Arrays.stream(p.getONumber().getOItemNumber()))
.filter(o -> o.getPItemDetails().getSItemDetails() != null).count();
OR
long sItemDetailsCount3 = Arrays.stream(pProductSuccess)
.map(p -> p.getONumber().getOItemNumber())
.flatMap(Arrays::stream)
.filter(Objects::nonNull)
.count();
When I executed the above codes it gave the result as 2 but i am expecting 4 since we have 4 SItemDetails in the pProductsResponse.
Can someone help me achieve it using lamda iteration.
A lambda expression is just an anonymous function. It doesn't look like lambda's are your issue here. It looks like you're confused on what your streams are doing.
I'd suggest reading through https://www.baeldung.com/java-8-streams-introduction to get a better understanding of using the streams api.
Your IDE should already do this for you but each method chained together below is returning an object. Map and flatmap both return Stream<>. I've added some comments of what I can guess is being returned from your method calls.
long sItemDetailsCount3 = Arrays.stream(pProductSuccess) //Stream<pProductSuccess>
.map(p -> p.getONumber().getOItemNumber()) //Stream<List<OItemNumber>>
.flatMap(Arrays::stream) // Stream<OItemNumber>
.filter(Objects::nonNull) // Stream<OItemNumber>
.count(); //The number of items in the Stream<OItemNumber>
As you can see, your count would be returning the count of OItemNumber's since that's what the stream is iterating over.
To get the SItemDetails we need to have a stream of SItemDetails. This should work for your needs. I am using flatmap since I assume your getters are returning a Collection<> of items. In short, flatmap will flatten the collection and give a Stream while map would give
Stream<List<item>>
Arrays.stream(pProductSuccess) //Stream<pProductSuccess>
.flatmap(p -> p.getONumber().getOItemNumber().stream()) //Stream<OItemDetails>
.flatmap(oItemNumber -> oItemNumber.getPItemDetails().stream())//Stream<PItemDetails>
.flatmap(pItemDetail -> pItemDetail.getSItemDetails().stream())//Stream<SItemDetails>
.filter(Objects::nonNull) // Stream<SItemDetails>
.count();
If you are ever confused about what is being returned from a stream, remove the method chaining and do something similar to
Stream<OItemNumber> oItemNumberStream = Arrays.stream(pProductSuccess)
.flatmap(p -> p.getONumber().getOItemNumber().stream())
Stream<> anotherStream = oItemNumberStream.flatmap(....);
By assigning local variables it is easier to see what object is returned from each call. Your IDE should help do this by displaying what would be returned on each line as long as you format your code so a method call is each on it's own line (as shown in your examples above).

Java 8 Streams reduce remove duplicates keeping the most recent entry

I have a Java bean, like
class EmployeeContract {
Long id;
Date date;
getter/setter
}
If a have a long list of these, in which we have duplicates by id but with different date, such as:
1, 2015/07/07
1, 2018/07/08
2, 2015/07/08
2, 2018/07/09
How can I reduce such a list keeping only the entries with the most recent date, such as:
1, 2018/07/08
2, 2018/07/09
?
Preferably using Java 8...
I've started with something like:
contract.stream()
.collect(Collectors.groupingBy(EmployeeContract::getId, Collectors.mapping(EmployeeContract::getId, Collectors.toList())))
.entrySet().stream().findFirst();
That gives me the mapping within individual groups, but I'm stuck as to how to collect that into a result list - my streams are not too strong I'm afraid...
Well, I am just going to put my comment here in the shape of an answer:
yourList.stream()
.collect(Collectors.toMap(
EmployeeContract::getId,
Function.identity(),
BinaryOperator.maxBy(Comparator.comparing(EmployeeContract::getDate)))
)
.values();
This will give you a Collection instead of a List, if you really care about this.
You can do it in two steps as follows :
List<EmployeeContract> finalContract = contract.stream() // Stream<EmployeeContract>
.collect(Collectors.toMap(EmployeeContract::getId,
EmployeeContract::getDate, (a, b) -> a.after(b) ? a : b)) // Map<Long, Date> (Step 1)
.entrySet().stream() // Stream<Entry<Long, Date>>
.map(a -> new EmployeeContract(a.getKey(), a.getValue())) // Stream<EmployeeContract>
.collect(Collectors.toList()); // Step 2
First step: ensures the comparison of dates with the most recent one mapped to an id.
Second step: maps these key, value pairs to a final List<EmployeeContract> as a result.
Just to complement the existing answers, as you're asking:
how to collect that into a result list
Here are some options:
Wrap the values() into an ArrayList:
List<EmployeeContract> list1 =
new ArrayList<>(list.stream()
.collect(toMap(EmployeeContract::getId,
identity(),
maxBy(comparing(EmployeeContract::getDate))))
.values());
Wrap the toMap collector into collectingAndThen:
List<EmployeeContract> list2 =
list.stream()
.collect(collectingAndThen(toMap(EmployeeContract::getId,
identity(),
maxBy(comparing(EmployeeContract::getDate))),
c -> new ArrayList<>(c.values())));
Collect the values to a new List using another stream:
List<EmployeeContract> list3 =
list.stream()
.collect(toMap(EmployeeContract::getId,
identity(),
maxBy(comparing(EmployeeContract::getDate))))
.values()
.stream()
.collect(toList());
With vavr.io you can do it like this:
var finalContract = Stream.ofAll(contract) //create io.vavr.collection.Stream
.groupBy(EmployeeContract::getId)
.map(tuple -> tuple._2.maxBy(EmployeeContract::getDate))
.collect(Collectors.toList()); //result is list from java.util package

How to collect objects from a doubly nested map in java 8?

I'm planning to leverage the benefits of using parallel streams in my program and I want to replace a loop like this:
for (int gridY = gridYFrom; gridY <= gridYTo; gridY++) {
for (int gridX = gridXFrom; gridX <= gridXTo; gridX++) {
coordinates.add(Coordinate.from(gridX, gridY));
}
}
with something like this:
IntStream.rangeClosed(gridYFrom, gridYTo).parallel().map(y ->
IntStream.rangeClosed(gridXFrom, gridXTo).mapToObj(x -> {
return Coordinate.from(x, y);
}
)).collect(Collectors.toSet());
My problem is that here I get a cyclic inference error. I understand that I am supposed to return an int from the inner map to be compatible with the outer one but I want to return a Coordinate object (thus the mapToObj call). Is it possible to do so using collect and without using a forEach construct?
What you want to do is these things:
First of all, when you call map on an IntStream, it still returns an IntStream, which isn't what you want. Instead, also use mapToObj for the outer loop.
Second of all, the inner loop returns an incomplete Stream<Coordinate>, which I assume is also not what you want. So, you'll want to call .collect(Collectors.toSet()) on that as well.
Finally, you'll want to flatMap the Stream<Set<Coordinate>> into a single Stream<Coordinate>, and you can do that by using
stream.flatmap(Set::stream);
This all boils down to
IntStream.rangeClosed(0, 10).parallel().mapToObj(y ->
IntStream.rangeClosed(0, 20).mapToObj(x ->
Coordinate.from(x,y)).collect(Collectors.toSet())
).flatMap(Set::stream).collect(Collectors.toSet());
EDIT:
Actually, forget the inner collect. Just flatmap to Stream::sequential.
You'll end up with
IntStream.rangeClosed(0, 10).parallel().mapToObj(y ->
IntStream.rangeClosed(0, 20).mapToObj(x ->
Coordinate.from(x, y))).flatMap(Stream::sequential).collect(Collectors.toSet())
You don't need to collect twice.
The problem is that IntStream isn't like any other stream in java, unfortunately. So, you have to convert it into a normal stream first, then you can do flatMap:
IntStream.rangeClosed(gridYFrom, gridYTo)
.mapToObj( y -> Integer.valueOf(y) )
.flatMap( y ->
IntStream.rangeClosed(gridXFrom, gridXTo)
.mapToObj(x -> Coordinate.from(x,y))
)
.collect(Collectors.toSet())

Categories