Подтвердить что ты не робот

Множественные проверки соответствия в одном потоке

Можно ли проверить, содержит ли массив (или коллекцию) элемент 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;
4b9b3361

Ответ 1

В этом конкретном случае, т.е. вы хотите знать, содержит ли поток или массив оба элемента соответствия и несимметричный элемент (элемент, соответствующий отрицанию предикатов), вы можете сделать это намного проще.

Сначала проверьте, соответствует ли первый элемент предикату или его отрицанию, затем найдите, содержит ли поток любое совпадение противоположного:

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

Ответ 2

Здесь два решения, включающие мою библиотеку StreamEx. Основная особенность, которую я здесь использую, - это концепция короткозамкнутых коллекционеров. Моя библиотека расширяет концепцию Collector, чтобы обеспечить возможность короткого замыкания (которая работает как для последовательных, так и для параллельных потоков)

Если предикаты похожи на ваш образец (один противоположный другому), вы можете использовать partitioningBy:

Map<Boolean, Optional<Integer>> 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 <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));

Ответ 3

Один из способов увидеть, как это сделать, - создать пользовательский IntPredicate из нескольких IntPredicate s. Каждый раз, когда значение проверяется, мы пытаемся найти предикат из этого массива, который соответствует ему, и если это произойдет, мы храним его внутри внутреннего Set (чтобы правильно обрабатывать дубликаты). Когда сохраненный набор имеет тот же размер, что и исходный массив, это означает, что все предикаты были сопоставлены, и наш пользовательский предикат может вернуть true.

В моем первоначальном решении использовался Set<Integer> для хранения индексов предикатов, которые были сопоставлены. Как комментировал @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 этот предикат будет работать корректно. Это также имеет то преимущество, что требуемые предикаты не должны быть противоположными себе.

Ответ 4

Если вы не возражаете использовать поток в штучной упаковке и достаточно 2 предикатов, вы можете использовать Collectors.partitioningBy и просто сделать что-то вроде:

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

Другое решение (для нескольких предикатов), которое, возможно, не так эффективно, как решение 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);