6

I want to group a collection but I don't want the aggregations to include some values. What's the best solution to do that?

A solution could be a collector filtering(Predicate,Collector) but there is no such collector. Is there any way to do it without implementing your own collector?

IntStream.range(0,100).collect(
    groupingBy(
       i -> i % 3,
       HashMap::new,
       filtering( i -> i % 2 == 0, toSet() )
    )
);
Marcin Król
  • 1,555
  • 2
  • 16
  • 31

2 Answers2

13

The only other way I'm aware of is to do the filtering beforehand, that is,

 stream.filter(filterPredicate).collect(collector)

That said, it's not clear what you're actually trying to do with your filtering function, which takes three arguments instead of two.

I suspect you are trying to map a function over elements that match a filter, but that's itself a map, which you can do easily enough:

 Collectors.mapping(input -> condition(input) ? function(input) : input, collector)

And making your own collector isn't actually that difficult:

static <T, A, R> Collector<T, A, R> filtering(
    Predicate<? super T> filter, Collector<T, A, R> collector) {
  return Collector.of(
      collector.supplier(),
      (accumulator, input) -> {
         if (filter.test(input)) {
            collector.accumulator().accept(accumulator, input);
         }
      },
      collector.combiner(),
      collector.finisher());
}
Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • The filtering function should take 2 arguments, corrected. The result is of type `Map>`. I want the Sets to contain only elements that satisfy predicate `i -> i % 2 == 0`. I can't filter the collection before grouping because then the result will not contain some groups(in general case). – Marcin Król Oct 07 '15 at 20:40
  • Then I think you're pretty much down to making your own collector as I described. – Louis Wasserman Oct 07 '15 at 20:43
6

In Java 9 there's a new filtering collector :

Map<Department, Set<Employee>> wellPaidEmployeesByDepartment = employees.stream()
  .collect(groupingBy(Employee::getDepartment,
      filtering(e -> e.getSalary() > 2000, toSet())));
Bax
  • 4,260
  • 5
  • 43
  • 65
  • This is better than a plain stream `filter()`, if you want the possibility for an empty group after filtering. Filtering before collecting would simply omit that group. – Noumenon Oct 31 '20 at 04:31