Comment appliquer des prédicats multiples à un java.util.Stream?

Comment appliquer des prédicats multiples à un java.util.Stream's filter() méthode?

C'est ce que je fais maintenant, mais je n'aime pas vraiment ça. J'ai un Collection de choses et je dois réduire le nombre de choses basées sur le Collection de filtres (prédicats):

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

je sais que si je savais le nombre de filtres à l'avant, je pouvais faire quelque chose comme ceci:

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

mais comment appliquer un nombre inconnu de prédicats sans mélanger les styles de programmation? Pour le savoir, il regarde genre de laid...

40
demandé sur Gunith D 2014-07-03 16:30:43

4 réponses

je suppose que votre Filter est un type distinct de java.util.function.Predicate, ce qui veut dire qu'il faut l'adapter. Une approche qui fonctionnera va comme ceci:

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

cela entraîne une légère performance de recréer le flux de filtre pour chaque évaluation de prédicat. Pour éviter que vous pouvez envelopper chaque filtre dans un Predicate et les composer:

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

cependant, depuis maintenant chaque filtre est derrière son propre Predicate, introduisant un niveau plus indirect, il est pas clair qui aurait une meilleure performance globale.

sans le souci d'adaptation (si votre Filter un Predicate) l'énoncé du problème devient beaucoup plus simple et la deuxième approche clairement l'emporte:

things.stream().filter(
   filtersCollection.stream().reduce(Predicate::or).orElse(t->true)
);
35
répondu Marko Topolnik 2014-07-06 18:17:58

Si vous avez un Collection<Predicate<T>> filters vous pouvez toujours créer un seul prédicat en utilisant le processus appelé réduction:

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

ou

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

selon la façon dont vous voulez combiner les filtres.

si le repli pour une collecte de prédicat vide spécifiée dans le orElse call remplit le rôle identitaire (qui x->trueand les prédicats et x->falseoring), vous pouvez également utiliser reduce(x->true, Predicate::and) ou reduce(x->false, Predicate::or) pour obtenir le filtre, mais c'est légèrement moins efficace pour les très petites collections car il combinerait toujours le prédicat d'identité avec le prédicat de la collection, Même si elle contient un seul prédicat. En revanche, la variante reduce(accumulator).orElse(fallback) montré ci-dessus retournera le prédicat unique si la collection a la taille 1.


Notez comment ce modèle s'applique à des problèmes similaires: Avoir un Collection<Consumer<T>> vous pouvez créer un seul Consumer<T> en utilisant

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

etc.

43
répondu Holger 2014-07-03 16:20:16

j'ai réussi à résoudre ce problème, si un utilisateur souhaite appliquer une liste de prédicats dans une opération de filtrage, une liste qui peut être dynamique, et pas donné, qui doit être réduit à un prédicat comme ceci:

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;

    }
}

notez que cela va les combiner avec un opérateur logique "et". Pour combiner avec un" ou " la ligne de réduction doit être:

Predicate<T> p = Stream.of(predicates).reduce(x -> false, Predicate::or);
7
répondu Uziel Sulkies 2017-10-02 13:11:26
http://www.leveluplunch.com/java/tutorials/006-how-to-filter-arraylist-stream-java8/). Je pense que c'est un moyen plus efficace.

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: Voici comment traiter les boucles où predicatesToIgnore est une liste de prédicats. Je crée une seule prédicatetoignore prédicat à partir de celui-ci.

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

alors, faites un filtre avec ce prédicat unique. Cela crée un meilleur filtre À mon humble avis

6
répondu Gunith D 2015-12-15 03:53:28