Nested collections lambda iteration - java

Suppose I have an object containing a collection, each elements on the said collection contains a collection, and each collection contains a collection.
And I want to iterate on the deepest objects and apply the same code to it.
The imperative way is trivial, but is there a way to lambda-fy this all?
Here is how the code looks today:
My object o;
SecretType computedThingy = 78;
for (FirstLevelOfCollection coll : o.getList()) {
for (SecondLevelOfCollection colColl : coll.getSet()) {
for (MyCoolTinyObjects mcto : colColl.getFoo()) {
mcto.setSecretValue(computedThingy);
}
}
}
I can see how to make a lambda out of the deepest loop:
colColl.getFoo().stream().forEach(x -> x.setSecretValue(computedThingy)
But can I do more?

flatMap is available for such a purpose. What you get here is iteration over all elements of the various deepest collections as if they were a single collection:
o.getList().stream()
.flatMap(c1 -> c1.getSet().stream())
.flatMap(c2 -> c2.getFoo().stream())
.forEach(x -> x.setSecretValue(computedThingy));

flatMap to the rescue, simple example with a nested collection of String
See also:
Java 8 Streams FlatMap method example
Turn a List of Lists into a List Using Lambdas
Set<List<List<String>>> outerMostSet = new HashSet<>();
List<List<String>> middleList = new ArrayList<>();
List<String> innerMostList = new ArrayList<>();
innerMostList.add("foo");
innerMostList.add("bar");
middleList.add(innerMostList);
List<String> anotherInnerMostList = new ArrayList<>();
anotherInnerMostList.add("another foo");
middleList.add(anotherInnerMostList);
outerMostSet.add(middleList);
outerMostSet.stream()
.flatMap(mid -> mid.stream())
.flatMap(inner -> inner.stream())
.forEach(System.out::println);
Produces
foo
bar
another foo

Related

Filtering values from a list in a java 8 stream

What we have is a list of objects of type Object, we might take them from a cache for example, so we want to iterate over that list with a lambda stream and after mapping an object in every iteration we want to see if attribute of that new class is present in a list of string values that we passed to the method.
Your solution is not optimised. Using List.contains will give you O(n*m) complexity. Use a HashSet instead:
public List<MyClass> getMyClassListByStates(List<String> states) {
Set<String> set = new HashSet<>(states);
return cache.getCacheByCacheNameList(CacheTypeConstants.MY_CLASS)
.stream()
.map(MyClass.class::cast)
.filter(myc -> set.contains(myc.getState()))
.collect(Collectors.toList());
}
This will run in O(max(n,m)) time instead of O(n*m).
This is my solution to that problem:
public List<MyClass> getMyClassListByStates(List<String> states) {
return cache.getCacheByCacheNameList(CacheTypeConstants.MY_CLASS)
.stream()
.map((myc) -> (MyClass) myc)
.filter(myc -> states.contains(myc.getState()))
.collect(Collectors.toList());
}
If someone has any other way to do it, please be free to comment, thx.

Best way to replace nested loop concatenate string using java stream

I have a list of names and a list of versions. I want to get all permutations which are constructed by concatenating the string from two lists. I am using two for loop to do this but I want to switch to a more functional style approach. Here is my solution:
List<String> names = new ArrayList<>();
List<String> versions = new ArrayList<>();
List<String> result = new ArrayList<>();
names.forEach(name -> versions.stream().map(version -> result.add(name.concat(version))));
Is there a better way to do it?
You are looking for the "Cartesian Product" of names and versions — basically the return set/list from the aforementioned sets/lists.
final Stream<List<String>> result = names.stream()
.flatMap(s1 -> versions.stream().flatMap(s2 -> Stream.of(Arrays.asList(s1, s2))));
result.forEach(System.out::println);
Keep in mind that operation is super expensive. Google's Guava have this implemented also under com.google.common.collect.Sets.cartesianProduct(s1, s2).
You should look forward to use flatMap while streaming over names and then performing map operation further correctly as:
List<String> result = names.stream() // for each name
.flatMap(name -> versions.stream() // for each version
.map(version -> name.concat(version))) // concat version to the name
.collect(Collectors.toList()); // collect all such names
Or a bit tidier:
final List<String> result = names.stream() // Stream the Names...
.flatMap(name -> versions.stream() // ...together with Versions.
.map (version -> name.concat(version))) // Combine Name+Version
.collect(Collectors.toList()); // & collect in List.

Lambda expression to add objects from one list to another type of list

There is a List<MyObject> and it's objects are required to create object that will be added to another List with different elements : List<OtherObject>.
This is how I am doing,
List<MyObject> myList = returnsList();
List<OtherObj> emptyList = new ArrayList();
for(MyObject obj: myList) {
OtherObj oo = new OtherObj();
oo.setUserName(obj.getName());
oo.setUserAge(obj.getMaxAge());
emptyList.add(oo);
}
I'm looking for a lamdba expression to do the exact same thing.
If you define constructor OtherObj(String name, Integer maxAge) you can do it this java8 style:
myList.stream()
.map(obj -> new OtherObj(obj.getName(), obj.getMaxAge()))
.collect(Collectors.toList());
This will map all objects in list myList to OtherObj and collect it to new List containing these objects.
You can create a constructor in OtherObject which uses MyObject attributes,
public OtherObject(MyObject myObj) {
this.username = myObj.getName();
this.userAge = myObj.getAge();
}
and you can do following to create OtherObjects from MyObjects,
myObjs.stream().map(OtherObject::new).collect(Collectors.toList());
I see that this is quite old post. However, this is my take on this based on the previous answers. The only modification in my answer is usage of .collect(ArrayList::new, ArrayList::add,ArrayList:addAll).
Sample code :
List<OtherObj> emptyList = myList.stream()
.map(obj -> {
OtherObj oo = new OtherObj();
oo.setUserName(obj.getName());
oo.setUserAge(obj.getMaxAge());
return oo; })
.collect(ArrayList::new, ArrayList::add,ArrayList::addAll);

Java 8 stream. all elements EXCEPT other elements

I'm interested in identifying an approach that returns a list of elements excluding the elements in another list.
for example
List<Integer> multiplesOfThree = ... // 3,6,9,12 etc
List<Integer> evens = ... // 2,4,6,8 etc
List<Integer> others = multiplesOfThree.except(evens) // should return a list of elements that are not in the other list
how do you do this?
i found an approach that's a bit clunky and difficult to read....
multiplesOfThree.stream()
.filter(intval -> evens.stream().noneMatch(even -> even.intValue() == intval.intValue()))
You can use Stream's filter method, passing a Predicate that ensures that the element doesn't exist in evens.
List<Integer> others = multiplesOfThree.stream()
.filter(i -> !evens.contains(i))
.collect(Collectors.toList());
But assuming you have a mutable List (e.g. ArrayList), you don't even need streams, just Collections's removeAll method.
multiplesOfThree.removeAll(evens);
You could use
multipleOfThree.stream()
.filter(((Predicate<Integer>) evens::contains).negate())
or more efficient for big even lists
HashSet<Integer> evenSet = new HashSet<>(even);
multipleOfThree.stream()
.filter(((Predicate<Integer>) evenSet::contains).negate())
There are a few solutions.
First, without using streams, you can just create a new list and remove all elements from another collection from it...
final List<Integer> multiplesOfThree = Arrays.asList(3,6,9,12);
final List<Integer> evens = Arrays.asList(2,4,6,8,10,12);
final List<Integer> others1 = new ArrayList<>(multiplesOfThree);
others1.removeAll(evens);
Another solution would be to pass the stream through a filter():
final List<Integer> others2 = multiplesOfThree
.stream()
.filter(x -> !evens.contains(x))
.collect(Collectors.toList());
(You may want to consider making evens a Set in this case).
And finally, you could modify the logic above to represent the "evens" as a function rather than a collection of all even numbers. This is essentially the same as above, but you don't have to have a second collection.
final List<Integer> others3 = multiplesOfThree
.stream()
.filter(x -> x % 2 != 0)
.collect(Collectors.toList());

Using predicate to filter a list

I have two array lists of objects, how can i use guava's filter to filter out that only the title of each object equal to each other? Each object has a getTitle() method.
List<Foo> listA;
List<Bar> listB;
for (Foo item: listA)
{
Iterables.filter(listB, new Predicate()
{
//predicate here
}
}
Using guava it can be done like this (without checking for null, and optimising on empty collection):
// If any of the arrays empty or null, you can return straight away
Set<String> titlesB = new HashSet<String>(Collections2.transform(listB, (b) -> b.getTitle()));
Set<String> titlesA = new HashSet<String>(Collections2.transform(listA, (a) -> a.getTitle()));
// You can further optimize checking the smallest collection (contains is O(1) operation on Set)
Set<String> titlesIntersection = Collections2.filter(titlesB, (b) -> titlesA.contains(b));
List<Foo> commonA = Collections2.filter(listA, (a) -> titlesIntersection.contains(a.getTitle()));
List<Foo> commonB = Collections2.filter(listB, (b) -> titlesIntersection.contains(b.getTitle()));
There is also, apache commons intersection function
https://commons.apache.org/proper/commons-collections/javadocs/api-3.2.1/org/apache/commons/collections/CollectionUtils.html
But you would need to use some additional step there anyway.

Categories