Fabrizio Stellato
Fabrizio Stellato

Reputation: 1891

java 11 lambda, aggregate by same property and the filter with a certain condition

I have the following entities:

ID    | PROPERTY

FOO   | A
FOO   | Z
JOHN  | A
DOE   | Z

Now I want to group these entities by ID and the take the one with property 'A' if one or both valuesare present , otherwise Z.

Thus the output should be:

FOO   | A
JOHN  | A
DOE   | Z

I was thinking about the groupingBy stream method, but I don't know how what collectors to use neither if is the correct solution:

list.stream()
            .collect(Collectors.groupingBy(o -> o.getId(), ? another collector function ?)
            .collect(Collectors.toList()));

Upvotes: 0

Views: 238

Answers (3)

Alexander Pavlov
Alexander Pavlov

Reputation: 2210

// Utility class with methods you can reuse for other purposes too
public final class StreamUtil {

    private StreamUtil() {}

    public static <T, U> Predicate<T> distinctBy(Function<T, U> keyFunction) {
        final Set<U> uniqueKeys = synchronizedSet(new HashSet<>());
        return item -> uniqueKeys.add(keyFunction.apply(item));
    }
}

and solution for your question

entities.stream()
 .sorting(Comparator.comparing(Entity::getProperty)) // order entities according to priorities
 .filter(StreamUtil.distinctBy(Entity::getId)) // take first entity only per key
 ... // here you have stream as per you requirements. You can collect() it or continue mapping/filtering

This solution is slower than other suggested here because of sorting. However, it has its advantage - easier to read, while slowness will kick in on large collections only.

Upvotes: 1

ETO
ETO

Reputation: 7279

Try this:

entities.stream()
        .collect(toMap(YourEntityType::getId, 
                       YourEntityType::getProperty,
                       (x, y) -> "A".equals(x) ? x : y));

Update:

I've just noticed that @Holger posted exactly the same thing in his comment, while I was editing my answer. There are two minor differences though:

  • I prefer using method references instead of lambdas (It's a matter of taste, but I find them less error prone than lambdas)
  • "A".equals(x) is safer than x.equals("A"). The latter will fail with NPE in case x is null.

Update 2:

Since you want the result in form of a list, then this option should work for you:

entities.stream()
        .collect(collectingAndThen(
                    toMap(YourEntityType::getId, 
                          identity(),
                          (x, y) -> "A".equals(x.getProperty()) ? x : y,
                    m -> new ArrayList<YourEntityType>(m.values()));

Upvotes: 1

If you insisted on using grouping collector, the ? another collector function ? would look like this:

    public static void main(String[] args) {
        record C(String id, String property) {}
        List<C> list = List.of(new C("FOO","A"),new C("FOO","Z"),new C("JOHN","A"),new C("DOE","Z"),new C("XXX","X"));
        System.out.println(list);
        Object result = list.stream()
                .collect(Collectors.groupingBy(o -> o.id, Collectors.collectingAndThen(Collectors.reducing((l,r) -> "A".equals(l.property) ? l : r), Optional::get)));
        System.out.println(result);     
    }

, which Idea suggest to correct to almost @ETO's solution.

(Apologize, having records I am lazy to use getters.)

Upvotes: 1

Related Questions