在一个流中进行多次“匹配”检查

是否可以检查数组(或集合)是否包含元素5 5以外的元素。在一个流中返回布尔结果而不是使用两个流:

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; 

这是涉及我的StreamEx库的两个解决方案。 我在这里使用的核心function是短路收集器的概念。 我的库增强了Collector概念,提供了短路function(适用于顺序和并行流)

如果谓词与样本中的一样(一个与另一个相反),则可以使用partitioningBy

 Map> map = IntStreamEx.of(ints).boxed() .partitioningBy(num -> num == 5, MoreCollectors.first()); 

现在您应该检查两个映射是否存在:

 System.out.println(map.values().stream().allMatch(Optional::isPresent)); 

或者在单一声明中:

 System.out.println(IntStreamEx.of(ints).boxed() .partitioningBy(num -> num == 5, MoreCollectors.first()) .values().stream().allMatch(Optional::isPresent)); 

这里我们使用MoreCollectors.first()短路收集器。 此解决方案类似于@ user140547提出的解决方案,但实际上只要找到两个元素就会停止处理。


对于两个自定义谓词,可以使用pairing收集器,它结合了两个收集器的结果(如果输入收集器是短路的,则保留短路)。 但首先,我们需要anyMatching收集器(我的库中没有):

 import static one.util.streamex.MoreCollectors.*; static  Collector anyMatching(Predicate pred) { return collectingAndThen(filtering(pred, first()), Optional::isPresent); } Collector hasFive = anyMatching(num -> num == 5); Collector hasNonFive = anyMatching(num -> num != 5); Collector hasBoth = pairing(hasFive, hasNonFive, (res1, res2) -> res1 && res2); System.out.println(IntStreamEx.of(ints).boxed().collect(hasBoth)); 

在这种特定情况下,即您想知道流或数组是否包含匹配和非匹配元素(与谓词的否定匹配的元素),您可以更简单地做到这一点。

首先,测试第一个元素是否与谓词或其否定匹配,然后,搜索流是否包含相反的任何匹配:

 IntPredicate predicate=i -> i==5; if(ints.length>0 && predicate.test(ints[0])) predicate=predicate.negate(); boolean result = IntStream.of(ints).anyMatch(predicate); 

而已。 如果你没有数组或集合作为流源,而是一个任意流,测试第一个元素有点棘手:

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

我认为如何做到这一点的一种方法是从多个IntPredicate创建一个自定义IntPredicate 。 每次测试一个值时,我们都会尝试从这个匹配它的数组中找到一个谓词,如果匹配,我们将它存储在一个内部Set (以正确处理重复)。 当存储集与初始数组具有相同的大小时,表示所有谓词都已匹配,并且我们的自定义谓词可以返回true

我的初始解决方案使用Set来存储匹配的谓词的索引。 正如@Holger评论的那样,使用BitSet并存储不匹配谓词的索引可能更具性能。

 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 } } 

像这样使用它:

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

对于数组的情况,就像你的问题一样,这个解决方案可能比在数组上迭代两次更有开销。 但是,在无限IntStream的情况下,此谓词仍将正常工作。 它还具有以下优点:所需谓词不必与其自身相反。

如果您不介意使用盒装流并且2个谓词就足够了,您可以使用Collectors.partitioningBy并执行以下操作:

  Map> collect = IntStream.of(ints).boxed().collect(Collectors.partitioningBy(x -> x == 5)); boolean hasFive = !collect.get(true).isEmpty(); boolean hasNonFive = !collect.get(false).isEmpty(); 

另一个解决方案(对于多个谓词)可能不像Tunaki的解决方案那样高,并且可能创建了太多的数组,但是没有使用可变的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);