Reputation: 968
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
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
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
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