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...
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)
);
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->true
and
les prédicats et x->false
or
ing), 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.
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);
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