Radosław Łazarz
Radosław Łazarz

Reputation: 968

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).

Upvotes: 2

Views: 3007

Answers (3)

Holger
Holger

Reputation: 298599

You can take the iterative solution of Tunaki’s answer to create a functional one which is not short-circuiting, but works in parallel:

@SafeVarargs
private static <T> boolean allMatchOneOf(Stream<T> stream, Predicate<T>... predicates) {
    int length = predicates.length;
    return stream.collect( () -> new BitSet(length),
    (bitSet,t) ->
      IntStream.range(0, length).filter(i -> !predicates[i].test(t)).forEach(bitSet::set),
    BitSet::or).nextClearBit(0)<length;
}

To simplify the code, this flips the meaning of the bits; setting a bit implies an unmatched predicate. So there’s a predicate fulfilled by all elements, if there is an unset bit within the range. If the predicates are rather expensive, you can use that information to only test predicates still fulfilled by all previous elements:

@SafeVarargs
private static <T> boolean allMatchOneOf(Stream<T> stream, Predicate<T>... predicates) {
    int length = predicates.length;
    return stream.collect( () -> new BitSet(length),
        (bitSet,t) -> {
            for(int bit=bitSet.nextClearBit(0); bit<length; bit=bitSet.nextClearBit(bit+1))
                if(!predicates[bit].test(t)) bitSet.set(bit);
        },
        BitSet::or).nextClearBit(0)<length;
}

It’s still not short-circuiting, but renders subsequent iterations to no-ops for failed predicates. It may still be unsatisfying if the source of the stream elements is expensive.

Note that you can use a similar improvement for the iterative solution:

@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);
    for(Iterator<T> it = stream.iterator(); it.hasNext() && !bitSet.isEmpty(); ) {
        T t = it.next();
        for(int bit=bitSet.nextSetBit(0); bit>=0; bit=bitSet.nextSetBit(bit+1))
            if(!predicates[bit].test(t)) bitSet.clear(bit);
    }
    return !bitSet.isEmpty();
}

The iterative solution already was short-circuiting in that it stops when there is no potentially matching predicate left, but still checked all predicates when there was at least one potentially matching predicate. With this improvement, it only checks predicates which have not failed yet and still exits when there is no candidate left.

Upvotes: 1

Tunaki
Tunaki

Reputation: 137329

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)));

Upvotes: 4

the8472
the8472

Reputation: 43160

I could perform this operation twice, but then the stream would be terminated and I would need to repeat the preliminary ones.

If you intend to process the elements only after checking your conditions then the stream will have to be buffered anyway since the condition can only be checked once all elements have been traversed.

So your options are generating the stream twice or putting it into a collection.

Upvotes: 0

Related Questions