1

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

Naman
  • 27,789
  • 26
  • 218
  • 353

2 Answers2

1

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
Naman
  • 27,789
  • 26
  • 218
  • 353
  • @a_ray_of_hope Note: The implementation of `public String getName() { return name; }` is to return a `String` instead of `A` as in your question. That said, it depends on how you are collecting the output as well because of the type-inference playing a vital role. But with your current minimal example, I could see it work on my local setup. Without the details of where is it failing and what error does it read, it's not possible to really say anything further. – Naman Dec 17 '19 at 17:15
0

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
Sunil Dabburi
  • 1,442
  • 12
  • 18
  • Ahh I see what you did there. Thank you! But this only returns one object of A, if there are two objects with the same enum type and the same name length, only one will be returned. How can you also return every object that has maximum length (compared with objects of the same enum type)? :/ – a_ray_of_hope Dec 16 '19 at 23:04
  • @SunilDabburi This does not solve for the *grouping* as the OP stated. It only ensures that all elements from the list with max length are chosen. Instead the filtering is at a level in, where all elements grouped in a list based on type `E` are filtered. – Naman Dec 17 '19 at 03:30
  • I don’t get it. I grouped them based on E and stored max length per E. And then filtered the list based on length per E for each entry – Sunil Dabburi Dec 17 '19 at 13:16