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

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())

Related

How to use Stream to get all Lists from a nested List that match a specific condition?

How can I achieve the same logic in my code using only Streams, without the for loop as shown in my code below?
I have tried using flatMap, but I get stuck on the condition part, since allMatch() only returns a boolean.
How can I retrieve all the rows from the nested ArrayList that passes the condition without using for loop?
ArrayList<ArrayList<Tile>> completeRows = new ArrayList<>();
for (ArrayList<Tile> rows: getGridTiles()) {
if (rows.stream().allMatch(p -> p.getOccupiedBlockType() != BlockType.EMPTY)) {
completeRows.add(rows);
}
}
You can apply filter() with a nested stream (exactly the same as you've used as a condition in your code) passed to it as a Predicate to verify that a list consists of only non-empty tiles.
And then collect all the lists (rows) that have passed the predicate into a List using collect().
public static List<List<Tile>> getNonEmptyRows(List<List<Tile>> rows) {
return rows.stream()
.filter(row -> row.stream().allMatch(tile -> tile.getOccupiedBlockType() != BlockType.EMPTY))
.collect(Collectors.toList()); // or .toList() with Java 16+
}
I have tried using flatMap
You need to use flatMap when your goal is to flatten the steam of collections (or objects holding a reference to a collection) to a stream of elements of these collections. In these case, turn a stream of lists of tiles Stream<List<Tile>> into a stream of tiles Stream<Tile>.
Judging by your code, it's not you what you want because you're accumulating the rows (lists of tiles) into another list and not "flattening" them.
But just in case, that's how it can be done:
public static List<Tile> getNonEmptyTiles(List<List<Tile>> rows) {
return rows.stream()
.filter(row -> row.stream().allMatch(tile -> tile.getOccupiedBlockType() != BlockType.EMPTY))
.flatMap(List::stream)
.collect(Collectors.toList()); // or .toList() with Java 16+
}
Sidenote: leverage abstract data types - write your code against interfaces. What does it mean to "program to an interface"?

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).

Create all possible combinations of elements

I need to create all possible combinations of some kind of Key, that is composed from X (in my case, 8), equally important elements. So i came up with code like this:
final LinkedList<Key> keys = new LinkedList();
firstElementCreator.getApplicableElements() // All creators return a Set of elements
.forEach( first -> secondElementCreator.getApplicableElements()
.forEach( second -> thirdElementCreator.getApplicableElements()
// ... more creators
.forEach( X -> keys.add( new Key( first, second, third, ..., X ) ) ) ) ) ) ) ) );
return keys;
and it's working, but there is X nested forEach and i have feeling that i'm missing out an easier/better/more elegant solution. Any suggestions?
Thanks in advance!
Is it Cartesian Product? Many libraries provide the API, for example: Sets and Lists in Guava:
List<ApplicableElements> elementsList = Lists.newArrayList(firstElementCreator, secondElementCreator...).stream()
.map(c -> c.getApplicableElements()).collect(toList());
List<Key> keys = Lists.cartesianProduct(elementsList).stream()
.map(l -> new Key(l.get(0), l.get(1), l.get(2), l.get(3), l.get(4), l.get(5), l.get(6), l.get(7))).collect(toList());
Since the number of input sets is fixed (it has to match the number of arguments in the Key constructor), your solution is actually not bad.
It's more efficient and easier to read without the lambdas, though, like:
for (Element first : firstElementCreator.getApplicableElements()) {
for (Element second : secondElementCreator.getApplicableElements()) {
for (Element third : thirdElementCreator.getApplicableElements()) {
keys.add(new Key(first, second, third));
}
}
}
The canonical solution is to use flatMap. However, the tricky part is to create the Key object from the multiple input levels.
The straight-forward approach is to do the evaluation in the innermost function, where every value is in scope
final List<Key> keys = firstElementCreator.getApplicableElements().stream()
.flatMap(first -> secondElementCreator.getApplicableElements().stream()
.flatMap(second -> thirdElementCreator.getApplicableElements().stream()
// ... more creators
.map( X -> new Key( first, second, third, ..., X ) ) ) )
.collect(Collectors.toList());
but this soon becomes impractical with deep nesting
A solution without deep nesting requires elements to hold intermediate compound values. E.g. if we define Key as
class Key {
String[] data;
Key(String... arg) {
data=arg;
}
public Key add(String next) {
int pos = data.length;
String[] newData=Arrays.copyOf(data, pos+1);
newData[pos]=next;
return new Key(newData);
}
#Override
public String toString() {
return "Key("+Arrays.toString(data)+')';
}
}
(assuming String as element type), we can use
final List<Key> keys =
firstElementCreator.getApplicableElements().stream().map(Key::new)
.flatMap(e -> secondElementCreator.getApplicableElements().stream().map(e::add))
.flatMap(e -> thirdElementCreator.getApplicableElements().stream().map(e::add))
// ... more creators
.collect(Collectors.toList());
Note that these flatMap steps are now on the same level, i.e. not nested anymore. Also, all these steps are identical, only differing in the actual creator, which leads to the general solution supporting an arbitrary number of Creator instances.
List<Key> keys = Stream.of(firstElementCreator, secondElementCreator, thirdElementCreator
/* , and, some, more, if you like */)
.map(creator -> (Function<Key,Stream<Key>>)
key -> creator.getApplicableElements().stream().map(key::add))
.reduce(Stream::of, (f1,f2) -> key -> f1.apply(key).flatMap(f2))
.apply(new Key())
.collect(Collectors.toList());
Here, every creator is mapping to the identical stream-producing function of the previous solution, then all are reduced to a single function combining each function with a flatMap step to the next one, and finally the resulting function is executed to get a stream, which is then collected to a List.

How to propagate variables in a stream in java 8

I would be currious to know how to propagate variable into a stream in java 8.
An example is better than a long explaination, so how would you convert the following (abstract) code into streams:
Map<Integer,A> myMap = new HashMap();
for (Entry<Integer,A> entry : myMap)
{
int param1=entry.getValue().getParam1();
List param2=entry.getValue().getParam2();
for (B b : param2)
{
System.out.println(""+entry.getKey()+"-"+param1+"-"+b.toString());
}
}
Knowing that this example is a simplification of the problem (for example, i need "param1" more than once in the next for loop)
So far, the only idea i have is to store all the informations i need into a tuple to finally use the forEach stream method over this tuple.
(Not sure to be very clear....)
Edit:I simplified my example too much. My case is more something like that:
Map<Integer,A> myMap = new HashMap();
for (Entry<Integer,A> entry : myMap)
{
int param1=entry.getValue().getParam1();
CustomList param2=entry.getValue().getParam2();
for (int i = 0; i<param2.size(); i++)
{
System.out.println(""+entry.getKey()+"-"+param1+"-"+param2.get(i).toString());
}
}
I could write something like that with stream:
myMap.entrySet().stream()
.forEach(
e -> IntStream.range(0, e.getValue.getParam2().getSize())
.forEach(
i -> System.out.println(e.getKey()+"-"+e.getValue().getParam1()+"-"+e.getValue.getParam2.get(i))
)
);
However, what i have instead of "e.getValue.getParam2()" in my real case is much more complex (a sequence of 5-6 methods) and heavier than just retrieving a variable (it executes some logic), so i would like to avoid to repeat e.getValue.getParam2 (once in just before the forEach, and once in the forEach)
i know that it's maybe not the best use case for using stream, but I am learning about it and would like to know about the limits
Thanks!
Something like this:
myMap.forEach(
(key, value) -> value.getParam2().forEach(
b -> System.out.println(key+"-"+value.getParam1()+"-"+b)
)
);
That is, for each key/value pair, iterate through value.getParam2(). For each one of those, print out string formatted as you specified. I'm not sure what that gets you, other than being basically what you had before, but using streams.
Update
Responding to updates to your question, this:
myMap.forEach((key, value) -> {
final CustomList param2 = value.getParam2();
IntStream.range(0, param2.getSize()).forEach(
i -> System.out.println(key+"-"+value.getParam1()+"-"+param2.get(i))
)
});
Here we assign the result of getParam2() to a final variable, so it is only calculated once. Final (and effectively final) variables are visible inside lambda functions.
(Thank you to Holger for the suggestions.)
Note that there are more features in the Java 8 API than just streams. Especially, if you just want to process all elements of a collection, you don’t need streams.
You can simplify every form of coll.stream().forEach(consumer) to coll.forEach(consumer). This applies to map.entrySet() as well, however, if you want to process all mappings of a Map, you can use forEach on the Map directly, providing a BiConsumer<KeyType,ValueType> rather than a Consumer<Map.Entry<KeyType,ValueType>>, which can greatly improve the readability:
myMap.forEach((key, value) -> {
int param1 = value.getParam1();
CustomList param2 = value.getParam2();
IntStream.range(0, param2.size()).mapToObj(param2::get)
.forEach(obj -> System.out.println(key+"-"+param1+"-"+obj));
});
It’s worth thinking about adding a forEach(Consumer<ElementType>) method to your CustomList, even if the CustomList doesn’t support the other standard collection operations…

Java lambda expression -- mapping and then modifying a list?

Using a Java 8 lambda expression, I'm trying to do something like this.
List<NewObject> objs = ...;
for (OldObject oldObj : oldObjects) {
NewObject obj = oldObj.toNewObject();
obj.setOrange(true);
objs.add(obj);
}
I wrote this code.
oldObjects.stream()
.map(old -> old.toNewObject())
.forEach({new.setOrange("true")})
.collect(Collectors.toList());
This is invalid code because I'm then trying to do .collect() on what's returned by .forEach(), but forEach is void and does not return a list.
How should this be structured?
You can use Stream's peek method, which returns the Stream because it's an intermediate operation. It normally isn't supposed to have a side effect (it's supposed to be "non-interfering"), but in this case, I think the side effect (setOrange(true)) is intended and is fine.
List<NewObject> newObjects =
oldObjects.stream()
.map(OldObject::toNewObject)
.peek( n -> n.setOrange(true))
.collect(Collectors.toList());
It's about as verbose as your non-streams code, so you can choose which technique to use.
You can use peek.
List<NewObject> list = oldObjects.stream()
.map(OldObject::toNewObject)
.peek(o -> o.setOrange(true))
.collect(Collectors.toList());
Alternatively, you can mutate the elements after forming the list.
List<NewObject> list = oldObjects.stream()
.map(OldObject::toNewObject)
.collect(Collectors.toList());
list.forEach(o -> o.setOrange(true));

Categories