Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple "match" checks in one stream

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;
like image 773
snp0k Avatar asked Dec 11 '15 09:12

snp0k


2 Answers

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));
like image 111
Tagir Valeev Avatar answered Sep 21 '22 11:09

Tagir Valeev


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]);
like image 25
Holger Avatar answered Sep 20 '22 11:09

Holger