Reputation: 23
So there is 1 class, and 1 Enum. The Enum is for instance:
public enum E {
E1, E2, E3
}
The class:
public class A {
private E e;
private String name;
public A (E e, String name){
this.e = e;
this.name = name;
}
public E getE(){
return E;
}
public String getName(){
return name;
}
}
Given is a List< A >. What I want is to filter the list (with stream) depending on their name and their enum-attribute. Filtered are only A's that don't have the maximum name length compared to the other objects that have the same enum-attribute.
The output should be a Map< E, List< A>>. List because there could be more than one object with the same name length.
Sample:
List<A> input = Arrays.asList(new A(E.E1, "abcd"), new A(E.E1, "xyz"), new A(E.E2, "abcd"), new A(E.E2, "wxyz"), new A(E.E3, "xyzb"));
This should return:
E1 = "abcd" // because "xyz" has length 3
E2 = "abcd", "wxyz" // as both have length 4, e.g. max length
E3 = "xyzb" // because it is the only object
One of my solution was you can group the list with "groupingBy" into their respective enum group. At the end you have then for instance 3 keys with a list of A's as value. Eventually you simply have to delete all the values that have a name shorter than the maximum name length. But only in their respective key
Upvotes: 1
Views: 633
Reputation: 31878
You can start by using nested groupingBy
to ensure grouped by attribute E
and further by length
of the name
. While grouping by name
, you can collect into a TreeMap
, since you are only interested in the entries with max
length of the name. This is to ensure that you could further have a mapping of E to the values of lastEntry
of the TreeMap.
The solution with the above approach would look like:
Map<E, List<A>> output = input.stream()
.collect(Collectors.groupingBy(A::getE,
Collectors.groupingBy(e -> e.getName().length(),
TreeMap::new, Collectors.toList()))) // Map<E, TreeMap<Integer, List<A>>> // the nested groupings
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey,
m -> m.getValue().lastEntry().getValue())); // only max length entry mapped here
Upvotes: 1
Reputation: 1472
You can try converting the list to map and then get filtered values
Assumption: A has constructor that takes E and Name as parameters. And I created getters for them.
List<A> input = Arrays.asList(new A(E.E1, "abcd"), new A(E.E1, "xyz"), new A(E.E2, "abcd"), new A(E.E2, "xyz"), new A(E.E3, "xyzb"));
Collection<A> result = input.stream()
.collect(Collectors.toMap(A::getE, Function.identity(), (n1, n2) -> (n1.getName().length() > n2.getName().length() ? n1 : n2)))
.values();
System.out.println(result);
Updated as per comments: To address scenario where two strings are of same length. It is not straight forward to handle in a single stream operation. We need to divide the work here.
Here is a way to do it:
List<A> input = Arrays.asList(new A(E.E1, "abcd"), new A(E.E1, "xyzd"), new A(E.E2, "abcd"), new A(E.E2, "xyz"), new A(E.E3, "xyzb"));
Map<E, Integer> lengthMap = input
.stream()
.collect(Collectors.toMap(A::getE, t -> t.getName().length(), (n1, n2) -> Math.max(n1, n2)));
List<A> result = input.stream()
.filter(a -> a.getName().length() == lengthMap.get(a.getE()))
.collect(Collectors.toList());
for (A a : result) {
System.out.println(a.getE() + " : " + a.getName());
}
Output:
E1 : abcd
E1 : xyzd
E2 : abcd
E3 : xyzb
Upvotes: 0