Radosław Łazarz Radosław Łazarz - 3 months ago 19
Java Question

Is it possible to check whether all Java 8 stream elements satify one of given predicates?

With stream API I could easily check whether all elements satify a given condition, using

allMatch(e -> predicate(e))
method. I could also check if any of multiple conditions is satified
allMatch(e -> predicateA(e) || predicateB(e) || predicateC(e))
. But is it possible to check if all elements satisfy one of those predicates (either one)? In the previous case it is possible that some elements satisfy A and some of them not, but they satisfy B or C (and vice versa).

I could perform
allMatch
multiple times, but then the stream would be terminated and I would need to repeat the preliminary ones.

I could also devise a tricky reduce operation, but then it would not be able to stop earlier, when the result is obviously false (like the
allMatch
method does).

Answer

Here is a possible approach, that goes back to using a simple Iterator over the elements of the Stream (so it doesn't have parallel support, but works for any kind and any number of predicates).

It creates an initial BitSet having the size of the given predicates' length with all bits set to true, and each time we retrieve a next element, we clear (set to false) the indexes of the predicates that didn't match. Thus, at each index, the bit set will contain whether that predicate all matched the element of the stream so far. It is short-circuiting because it loops until there are elements left and the bit set is not empty (meaning there are still predicates that all matched the elements considered so far).

@SafeVarargs
private static <T> boolean allMatchOneOf(Stream<T> stream, Predicate<T>... predicates) {
    int length = predicates.length;
    BitSet bitSet = new BitSet(length);
    bitSet.set(0, length);
    Iterator<T> it = stream.iterator();
    while (it.hasNext() && !bitSet.isEmpty()) {
        T t = it.next();
        IntStream.range(0, length).filter(i -> !predicates[i].test(t)).forEach(bitSet::clear);
    }
    return !bitSet.isEmpty();
}

Sample usage:

// false because not all elements are either even or divisible by 3
System.out.println(allMatchOneOf(Stream.of(2, 3, 12), i -> i % 2 == 0, i -> i % 3 == 0));

// true because all elements are divisible by 3
System.out.println(allMatchOneOf(Stream.of(3, 12, 18), i -> i % 2 == 0, i -> i % 3 == 0));

If we want to keep parallel support, we can have help from the StreamEx library, that has filtering, first and pairing collectors. We reuse the anyMatching collector wrote in this answer.

import static one.util.streamex.MoreCollectors.*;

@SafeVarargs
static <T> Collector<T, ?, Boolean> allMatchingOneOf(Predicate<T> first, Predicate<T>... predicates) {
    Collector<T, ?, Boolean> collector = allMatching(first);
    for (Predicate<T> predicate : predicates) {
        collector = pairing(collector, allMatching(predicate), Boolean::logicalOr);
    }
    return collector;
}

static <T> Collector<T, ?, Boolean> allMatching(Predicate<T> pred) {
    return collectingAndThen(anyMatching(pred.negate()), b -> !b);
}

static <T> Collector<T, ?, Boolean> anyMatching(Predicate<T> pred) {
    return collectingAndThen(filtering(pred, first()), Optional::isPresent);
}

The new allMatchingOneOf collector combines the result of each allMatching collecting result by performing a logical OR on it. As such, it will tell whether all elements of the stream matched one of the given predicates.

Sample usage:

// false because not all elements are either even or divisible by 3
System.out.println(Stream.of(2, 3, 12).collect(allMatchingOneOf(i -> i % 2 == 0, i -> i % 3 == 0)));

// true because all elements are divisible by 3
System.out.println(Stream.of(3, 12, 18).collect(allMatchingOneOf(i -> i % 2 == 0, i -> i % 3 == 0)));