问题
Is it possible to check if an array (or collection) contains element 5 and element other than 5. In one stream returning boolean result instead of using two streams:
int[] ints = new int[]{1, 2, 3, 4, 5};
boolean hasFive = IntStream.of(ints).anyMatch(num -> num == 5);
boolean hasNonFive = IntStream.of(ints).anyMatch(num -> num != 5);
boolean result = hasFive && hasNonFive;
回答1:
Here's two solutions involving my StreamEx library. The core feature I'm using here is the concept of short-circuiting collectors. My library enhances the Collector
concept to provide the ability to short-circuit (which works both for sequential and parallel streams)
If predicates are like in your sample (one is the opposite of another), you may use partitioningBy
:
Map<Boolean, Optional<Integer>> map = IntStreamEx.of(ints).boxed()
.partitioningBy(num -> num == 5, MoreCollectors.first());
Now you should check whether both mappings are present:
System.out.println(map.values().stream().allMatch(Optional::isPresent));
Or in single statement:
System.out.println(IntStreamEx.of(ints).boxed()
.partitioningBy(num -> num == 5, MoreCollectors.first())
.values().stream().allMatch(Optional::isPresent));
Here we're using MoreCollectors.first() short-circuiting collector. This solution is similar to one proposed by @user140547, but it will actually stop processing as soon as both elements are found.
For two custom predicates it's possible to use pairing collector which combines the results of two collectors (preserving the short-circuiting if input collectors are short-circuiting). But first, we need anyMatching
collector (which is absent in my library):
import static one.util.streamex.MoreCollectors.*;
static <T> Collector<T, ?, Boolean> anyMatching(Predicate<T> pred) {
return collectingAndThen(filtering(pred, first()), Optional::isPresent);
}
Collector<Integer, ?, Boolean> hasFive = anyMatching(num -> num == 5);
Collector<Integer, ?, Boolean> hasNonFive = anyMatching(num -> num != 5);
Collector<Integer, ?, Boolean> hasBoth = pairing(hasFive, hasNonFive,
(res1, res2) -> res1 && res2);
System.out.println(IntStreamEx.of(ints).boxed().collect(hasBoth));
回答2:
In this specific case, i.e. you want to know whether a stream or array contains both, a matching and a nonmatching element (an element matching the predicate’s negation), you can do it much simpler.
First, test whether the first element matches the predicate or its negation, then, search whether the stream contains any match of the opposite:
IntPredicate predicate=i -> i==5;
if(ints.length>0 && predicate.test(ints[0]))
predicate=predicate.negate();
boolean result = IntStream.of(ints).anyMatch(predicate);
That’s it. In case you don’t have an array or collection as the stream source, but an arbitrary stream, testing the first element is a bit trickier:
IntPredicate[] tmp={ null };
Spliterator.OfInt sp=intStream.spliterator();
boolean result = sp.tryAdvance(
(int i) -> tmp[0]=predicate.test(i)? predicate.negate(): predicate)
&& StreamSupport.intStream(sp, false).anyMatch(tmp[0]);
回答3:
One way I see how to do that is to create a custom IntPredicate
from multiple IntPredicate
s. Each time a value is tested, we try to find a predicate from this array that matches it and if it does, we store it inside an internal Set
(to handle duplicates correctly). When the stored set has the same size as the initial array, it means all the predicates have been matched and our custom predicate can return true
.
My initial solution used a Set<Integer>
to store the indexes of the predicates that were matched. As @Holger commented, it may be more performant to use a BitSet
and store the indexes of the unmatched predicates.
private static class MultipleIntPredicate implements IntPredicate {
private IntPredicate[] predicates;
private BitSet unmatchedPredicates;
public MultipleIntPredicate(IntPredicate... predicates) {
this.predicates = predicates;
unmatchedPredicates = new BitSet(predicates.length);
unmatchedPredicates.set(0, predicates.length, true); // initially, all predicates are unmatched
}
@Override
public boolean test(int value) {
unmatchedPredicates.stream()
.filter(i -> predicates[i].test(value))
.findFirst()
.ifPresent(unmatchedPredicates::clear); // when a match is found, clear the BitSet
return unmatchedPredicates.isEmpty(); // return true if all the predicates were matched
}
}
Using it like this:
int[] ints = new int[] {1, 2, 3, 4, 5};
MultipleIntPredicate predicate = new MultipleIntPredicate(num -> num == 5, num -> num != 5);
boolean hasFiveAndNonFive = IntStream.of(ints).anyMatch(predicate);
System.out.println(hasFiveAndNonFive);
For the case of an array, like in your question, this solution is probably more overhead than iterating over the array twice. However, in the case of an infinite IntStream
, this predicate will still work correctly. It also has the advantage that the wanted predicates do not have to be opposite of themselves.
回答4:
If you don't mind using a boxed stream and 2 predicates are enough, you can use Collectors.partitioningBy
and just do something like:
Map<Boolean, List<Integer>> collect = IntStream.of(ints).boxed().collect(Collectors.partitioningBy(x -> x == 5));
boolean hasFive = !collect.get(true).isEmpty();
boolean hasNonFive = !collect.get(false).isEmpty();
Another solution (for multiple predicates) which is maybe not as performant as Tunaki's solution and probably creates too many arrays, but does not use a mutable BitSet
...
Boolean[] result = IntStream.of(ints).mapToObj(i ->
new Boolean[]{four.test(i), five.test(i), six.test(i)}
).reduce(new Boolean[]{false, false, false}, Test::or);
来源:https://stackoverflow.com/questions/34219656/multiple-match-checks-in-one-stream