58

How can I apply multiple predicates to a java.util.Stream's filter() method?

This is what I do now, but I don't really like it. I have a Collection of things, and I need to reduce the number of things based on the Collection of filters (predicates):

Collection<Thing> things = someGenerator.someMethod();
List<Thing> filtered = things.parallelStream().filter(p -> {
   for (Filter f : filtersCollection) {
      if (f.test(p))
        return true;
   }
   return false;
}).collect(Collectors.toList());

I know that if I knew number of filters up-front, I could do something like this:

List<Thing> filtered = things.parallelStream().filter(filter1).or(filter2).or(filter3)).collect(Collectors.toList());

But how can I apply an unknown number of predicates without mixing programming styles? For now, it looks sort of ugly...

vatbub
  • 2,713
  • 18
  • 41
Paweł Dyda
  • 18,366
  • 7
  • 57
  • 79
  • 1
    [possible duplicate](http://stackoverflow.com/q/24396544/1113392) – A4L Jul 03 '14 at 12:34
  • 1
    @A4L: It's not a duplicate, I am asking how to use multiple filters when you don't know how many filters there will be. If it was only one ore two, I already know the answer. – Paweł Dyda Jul 03 '14 at 12:36
  • You can build the composite filter using `.or` in a loop. – Marko Topolnik Jul 03 '14 at 12:39
  • 1
    How do you create list of predicates? Can't you use instead something like `Predicate allPredicates` and add to it rest of predicates thanks to `allPredicates.and(predicate);` or `allPredicates.or(predicate);` and then use `allPredicates` in `filter`? – Pshemo Jul 03 '14 at 12:40
  • @Pshemo: That's exactly what I am asking about. How to create this on the fly? I will have a list of predicates created by external consumer of the class and I need to create sort of uber-predicate. That's what I did actually, but it doesn't look good. – Paweł Dyda Jul 03 '14 at 12:43
  • @PawełDyda well you could try to build `PredicateCain`, something like apache's ComparatorCahin with a variable length argument – A4L Jul 03 '14 at 12:45

4 Answers4

67

If you have a Collection<Predicate<T>> filters you can always create a single predicate out of it using the process called reduction:

Predicate<T> pred=filters.stream().reduce(Predicate::and).orElse(x->true);

or

Predicate<T> pred=filters.stream().reduce(Predicate::or).orElse(x->false);

depending on how you want to combine the filters.

If the fallback for an empty predicate collection specified in the orElse call fulfills the identity role (which x->true does for anding the predicates and x->false does for oring) you could also use reduce(x->true, Predicate::and) or reduce(x->false, Predicate::or) to get the filter but that’s slightly less efficient for very small collections as it would always combine the identity predicate with the collection’s predicate even if it contains only one predicate. In contrast, the variant reduce(accumulator).orElse(fallback) shown above will return the single predicate if the collection has size 1.


Note how this pattern applies to similar problems as well: Having a Collection<Consumer<T>> you can create a single Consumer<T> using

Consumer<T> c=consumers.stream().reduce(Consumer::andThen).orElse(x->{});

Etc.

Holger
  • 285,553
  • 42
  • 434
  • 765
54

I am assuming your Filter is a type distinct from java.util.function.Predicate, which means it needs to be adapted to it. One approach which will work goes like this:

things.stream().filter(t -> filtersCollection.stream().anyMatch(f -> f.test(t)));

This incurs a slight performance hit of recreating the filter stream for each predicate evaluation. To avoid that you could wrap each filter into a Predicate and compose them:

things.stream().filter(filtersCollection.stream().<Predicate>map(f -> f::test)
                       .reduce(Predicate::or).orElse(t->false));

However, since now each filter is behind its own Predicate, introducing one more level of indirection, it is not clear-cut which approach would have better overall performance.

Without the adapting concern (if your Filter happens to be a Predicate) the problem statement becomes much simpler and the second approach clearly wins out:

things.stream().filter(
   filtersCollection.stream().reduce(Predicate::or).orElse(t->true)
);
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • 2
    I don’t get what you want to say with “each filter is behind its own `Predicate`”. The term “filter” and the type `Predicate` have the same meaning here. – Holger Jul 04 '14 at 10:47
  • 2
    When writing the answer, I assumed that OP has some custom "Filter", which is not formally the same type as `Predicate`, so a conversion step is needed which wraps OP's Filter into a Predicate, creating a level of indirection. If indeed `Filter implements Predicate`, then the situation is much simpler. – Marko Topolnik Jul 04 '14 at 11:07
  • 1
    I see. But I don’t think that it was the intention of the OP to introduce a new predicate type. There’s nothing in the wording of the entire question suggesting that. There’s only one place where `Filter` occurs, I guess it’s just an oversight and meant to be `Predicate` anyway. – Holger Jul 04 '14 at 11:45
  • Had I started with that premise, I would never have written the first solution, which I came up with only because I couldn't quickly think of a concise way to adapt `Filter` to `Predicate`. – Marko Topolnik Jul 04 '14 at 11:47
  • 1
    Maybe it’s better to name that ambiguity of the question in the answer to make clear under which circumstances which solution is to prefer. I simply overlooked the one occurrence of `Filter` that’s why I was confused by your statement. But maybe `Filter` *is* intentional, who (besides the OP) knows… – Holger Jul 04 '14 at 11:53
  • +1 for `anyMatch()` / `allMatch()`, which is just enough when all you need to do is to test a list of `Predicates` on a single element -- there, the chaining by `reduce()` would be just an overkill. – charlie Jun 28 '16 at 16:56
15

I've managed to solve such a problem, if a user wants to apply a list of predicates in one filter operation, a list which can be dynamic and not given, that should be reduced into one predicate - like this:

public class TestPredicates {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        System.out.println(numbers.stream()
                .filter(combineFilters(x -> x > 2, x -> x < 9, x -> x % 2 == 1))
                .collect(Collectors.toList()));
    }

    public static <T> Predicate<T> combineFilters(Predicate<T>... predicates) {

        Predicate<T> p = Stream.of(predicates).reduce(x -> true, Predicate::and);
        return p;

    }
}

Note that this will combine them with an "AND" logic operator. To combine with an "OR" the reduce line should be:

Predicate<T> p = Stream.of(predicates).reduce(x -> false, Predicate::or);
Uziel Sulkies
  • 598
  • 5
  • 15
10

This is an interesting way of solving this problem, (directly pasting from http://www.leveluplunch.com/java/tutorials/006-how-to-filter-arraylist-stream-java8/). I think this is a more efficient way.

Predicate<BBTeam> nonNullPredicate = Objects::nonNull;
Predicate<BBTeam> nameNotNull = p -> p.teamName != null;
Predicate<BBTeam> teamWIPredicate = p -> p.teamName.equals("Wisconsin");

Predicate<BBTeam> fullPredicate = nonNullPredicate.and(nameNotNull)
        .and(teamWIPredicate);

List<BBTeam> teams2 = teams.stream().filter(fullPredicate)
        .collect(Collectors.toList());

EDIT: Here's how to deal with loops where predicatesToIgnore is a List of predicates. I create a single predicate predicateToIgnore from it.

Predicate<T> predicateToIgnore = null;
for (Predicate<T> predicate : predicatesToIgnore) {
    predicateToIgnore = predicateToIgnore == null ? predicate : predicateToIgnore.or(predicate);
}

Then, do a filter with this single predicate. This creates a better filter IMHO

Gunith D
  • 1,843
  • 1
  • 31
  • 36
  • 2
    I am afraid, it doesn't solve the problem in question - you know the number of predicates up front. I asked about the situation when I don't know the number of predicates (it could possibly be infinite). – Paweł Dyda Dec 14 '15 at 13:54
  • 2
    I agree.. But we can make a loop and link them up using OR (or AND) – Gunith D Dec 15 '15 at 03:30