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


Comment puis-je appliquer plusieurs prédicats à 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 en fonction du 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 connaissais le nombre de filtres à l'avance, je pourrais faire quelque chose comme ceci:

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

Mais comment puis-je appliquer un nombre inconnu de prédicats sans mélanger les styles de programmation? Pour savoir qu'il semble un peu laid...

Author: Gunith D, 2014-07-03

4 answers

Je suppose que votre Filter est un type distinct de java.util.function.Predicate, ce qui signifie qu'il doit y être adapté. Une approche qui fonctionnera va comme ceci:

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

Cela entraîne un léger impact sur les performances de la recréation du flux de filtre pour chaque évaluation de prédicat. Pour éviter cela, vous pouvez envelopper chaque filtre dans un {[5] } et les composer:

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

Cependant, puisque maintenant chaque filtre est derrière son propre Predicate, introduisant un niveau supplémentaire d'indirection, il n'est pas clair quelle approche aurait une meilleure performance globale.

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

things.stream().filter(
   filtersCollection.stream().reduce(Predicate::or).orElse(t->true)
);
 35
Author: 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 procédé appelé réduction de:

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 souhaitez combiner les filtres.

Si le repli pour une collection de prédicats vide spécifiée dans l'appel orElse remplit le rôle d'identité (ce que x->true fait pour and ing les prédicats et x->false fait pour oring), 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 s'il ne contient qu'un seul prédicat. En revanche, la variante reduce(accumulator).orElse(fallback) montrée ci-dessus retournera le prédicat unique si la collection a une taille 1.


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

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

Etc.

 43
Author: Holger, 2014-07-03 16:20:16

J'ai réussi à résoudre un tel problème, si un utilisateur veut appliquer une liste de prédicats dans une opération de filtre, une liste qui peut être dynamique et non donnée, qui devrait être réduite en 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 les combinera 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
Author: Uziel Sulkies, 2017-10-02 13:11:26

C'est une façon intéressante de résoudre ce problème, (coller directement à partir de 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 un seul prédicat predicateToIgnore à partir de celui-ci.

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

Ensuite, faites un filtre avec ce prédicat unique. Cela crée un meilleur filtre À MON HUMBLE avis

 6
Author: Gunith D, 2015-12-15 03:53:28