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

二次信任 提交于 2019-12-30 11:09:40

问题


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


回答1:


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



回答2:


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.




回答3:


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.



来源:https://stackoverflow.com/questions/38903844/is-it-possible-to-check-whether-all-java-8-stream-elements-satify-one-of-given-p

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!