Java8, Is there a filtering collector?
Asked Answered
I

2

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() )
    )
);
Insidious answered 7/10, 2015 at 20:28 Comment(0)
M
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());
}
Mural answered 7/10, 2015 at 20:31 Comment(2)
The filtering function should take 2 arguments, corrected. The result is of type Map<Integer, Set<Integer>>. 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).Paroicous
Then I think you're pretty much down to making your own collector as I described.Mural
A
7

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())));
Anapest answered 15/12, 2017 at 22:33 Comment(1)
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.Hinayana

© 2022 - 2024 — McMap. All rights reserved.