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

Как применить несколько предикатов к java.util.Stream?

Как я могу применить несколько предикатов к методу java.util.Stream's filter()?

Это то, что я делаю сейчас, но мне это не очень нравится. У меня есть Collection вещей, и мне нужно уменьшить количество вещей на основе Collection фильтров (предикатов):

Collection<Thing> things = someGenerator.someMethod();
List<Thing> filtered = things.parallelStream().filter(p -> {
   for (Filter f : filtersCollection) {
      if (f.test(p))
        return true;
   }
   return false;
}).collect(Collectors.toList());

Я знаю, что если бы я знал количество фильтров вверх, я мог бы сделать что-то вроде этого:

List<Thing> filtered = things.parallelStream().filter(filter1).or(filter2).or(filter3)).collect(Collectors.toList());

Но как я могу применить неизвестное число предикатов без смешивания стилей программирования? Знайте, что это выглядит как-то уродливо...

4b9b3361

Ответ 1

Я предполагаю, что ваш Filter - это тип, отличный от java.util.function.Predicate, что означает, что он должен быть адаптирован к нему. Один из подходов, который будет работать, выглядит следующим образом:

things.stream().filter(t -> filtersCollection.stream().anyMatch(f -> f.test(t)));

Это приводит к небольшому результативному результату воссоздания потока фильтра для каждой оценки предикатов. Чтобы избежать этого, вы можете обернуть каждый фильтр в Predicate и составить их:

things.stream().filter(filtersCollection.stream().<Predicate>map(f -> f::test)
                       .reduce(Predicate::or).orElse(t->false));

Однако, поскольку теперь каждый фильтр находится за его собственным Predicate, вводя еще один уровень косвенности, неясно, какой подход будет иметь лучшую общую производительность.

Без проблем адаптации (если ваш Filter оказывается Predicate), утверждение проблемы становится намного проще, и второй подход явно выигрывает:

things.stream().filter(
   filtersCollection.stream().reduce(Predicate::or).orElse(t->true)
);

Ответ 2

Если у вас есть Collection<Predicate<T>> filters, вы всегда можете создать из него один предикат, используя процесс, называемый сокращением:

Predicate<T> pred=filters.stream().reduce(Predicate::and).orElse(x->true);

или

Predicate<T> pred=filters.stream().reduce(Predicate::or).orElse(x->false);

в зависимости от того, как вы хотите объединить фильтры.

Если резервная копия для пустого набора предикатов, указанного в вызове orElse, выполняет роль идентичности (для x->true для and для предикатов и x->false для or ing) вы также можете использовать reduce(x->true, Predicate::and) или reduce(x->false, Predicate::or), чтобы получить фильтр, но это немного менее эффективно для очень маленьких коллекций, поскольку он всегда будет комбинировать предикат идентификации с предикатом коллекций, даже если он содержит только один предикат. Напротив, вариант reduce(accumulator).orElse(fallback), показанный выше, вернет единственный предикат, если коллекция имеет размер 1.


Обратите внимание, что этот шаблон применим и к аналогичным проблемам: имея Collection<Consumer<T>>, вы можете создать один Consumer<T>, используя

Consumer<T> c=consumers.stream().reduce(Consumer::andThen).orElse(x->{});

Etc.

Ответ 3

Это интересный способ решения этой проблемы (прямое вставка http://www.leveluplunch.com/java/tutorials/006-how-to-filter-arraylist-stream-java8/). Я думаю, что это более эффективный способ.

Predicate<BBTeam> nonNullPredicate = Objects::nonNull;
Predicate<BBTeam> nameNotNull = p -> p.teamName != null;
Predicate<BBTeam> teamWIPredicate = p -> p.teamName.equals("Wisconsin");

Predicate<BBTeam> fullPredicate = nonNullPredicate.and(nameNotNull)
        .and(teamWIPredicate);

List<BBTeam> teams2 = teams.stream().filter(fullPredicate)
        .collect(Collectors.toList());

EDIT: здесь, как иметь дело с циклами, где predicatesToIgnore - это список предикатов. Я создаю из него предикат predicateToIgnore.

Predicate<T> predicateToIgnore = null;
for (Predicate<T> predicate : predicatesToIgnore) {
    predicateToIgnore = predicateToIgnore == null ? predicate : predicateToIgnore.or(predicate);
}

Затем сделайте фильтр с этим единственным предикатом. Это создает лучший фильтр IMHO

Ответ 4

Мне удалось решить такую ​​проблему, если пользователь хочет применить список предикатов в одной операции фильтра, список, который может быть динамическим и не указан, который должен быть сведен к одному предикату - например:

public class TestPredicates {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        System.out.println(numbers.stream()
                .filter(combineFilters(x -> x > 2, x -> x < 9, x -> x % 2 == 1))
                .collect(Collectors.toList()));
    }

    public static <T> Predicate<T> combineFilters(Predicate<T>... predicates) {

        Predicate<T> p = Stream.of(predicates).reduce(x -> true, Predicate::and);
        return p;

    }
}

Обратите внимание, что это объединит их с логическим оператором "AND". Для объединения с "ИЛИ" линия сокращения должна быть:

Predicate<T> p = Stream.of(predicates).reduce(x -> false, Predicate::or);