a_ray_of_hope
a_ray_of_hope

Reputation: 23

Filter a Stream depending on String length and attribute

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

Answers (2)

Naman
Naman

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

Sunil Dabburi
Sunil Dabburi

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

Related Questions