web-dev-qa-db-fra.com

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

Comment appliquer plusieurs prédicats à une méthode Java.util.Stream'sfilter()?

C'est ce que je fais maintenant, mais je ne l'aime pas vraiment. J'ai un Collection d'objets et j'ai besoin de réduire le nombre d'objets basés 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 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 appliquer un nombre inconnu de prédicats sans mélanger les styles de programmation? Pour le savoir, ça a l'air moche ...

48
Paweł Dyda

Je suppose que votre Filter est un type distinct de Java.util.function.Predicate, ce qui signifie qu’il doit être adapté. 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 perte de performance en recréant le flux de filtre pour chaque évaluation de prédicat. Pour éviter cela, 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, comme chaque filtre est maintenant derrière son propre Predicate, introduisant un niveau supplémentaire d'indirection, le choix d'une approche offrant une meilleure performance globale n'est pas clairement défini.

Sans le problème 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)
);
41
Marko Topolnik

Si vous avez un Collection<Predicate<T>> filters, Vous pouvez toujours créer un prédicat unique 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);

en fonction de la manière dont vous souhaitez combiner les filtres.

Si la solution de secours pour une collection de prédicats vide spécifiée dans l'appel orElse remplit le rôle d'identité (ce que x->true Remplit pour anding les prédicats et x->false Remplit pour oring) vous pouvez également utiliser reduce(x->true, Predicate::and) ou reduce(x->false, Predicate::or) pour obtenir le filtre, mais cela 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) ci-dessus renvoie le prédicat unique si la collection a la taille 1.


Notez également comment ce modèle s’applique à des problèmes similaires: avec 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.

48
Holger

J'ai réussi à résoudre un tel problème, si un utilisateur souhaite appliquer une liste de prédicats en une opération de filtrage, liste pouvant être dynamique et non donnée, qui devrait être réduite à 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);
8
Uziel Sulkies

C’est une façon intéressante de résoudre ce problème (coller directement depuis 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ù prédicatsToIgnore est une liste de prédicats. Je crée un prédicat prédicatToIgnore à 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 IMHO

7
Gunith D