it-swarm.com.ru

Как применить несколько предикатов к 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());
_

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

48
Paweł Dyda

Я предполагаю, что ваш 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)
);
41
Marko Topolnik

Если у вас есть _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_ делает для anding предикатов, а _x->false_ делает для oring) вы также можете использовать 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->{});
_

И т.п.

48
Holger

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

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;

    }
}

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

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

Это интересный способ решения этой проблемы (непосредственно вставка из 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());

Правка: Вот как бороться с циклами, где предикаты - это список предикатов. Я создаю один предикат предиката ToIgnore из него.

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

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

7
Gunith D