_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());
_
しかし、プログラミングスタイルを混在させずに、未知の数の述語をどのように適用できますか?知っていると、それはちょっといように見えます...
私はあなたのFilter
がJava.util.function.Predicate
とは異なるタイプであると仮定しています。つまり、それに適応する必要があります。動作する1つのアプローチは次のようになります。
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
の背後にあるため、さらに1レベルの間接参照が導入されるため、全体的なパフォーマンスが向上するアプローチは明確ではありません。
適応の懸念がない場合(Filter
がPredicate
である場合)、問題のステートメントははるかに単純になり、2番目のアプローチが明らかに勝利します。
things.stream().filter(
filtersCollection.stream().reduce(Predicate::or).orElse(t->true)
);
Collection<Predicate<T>> filters
がある場合は、reductionと呼ばれるプロセスを使用して、その中からいつでも単一の述語を作成できます。
Predicate<T> pred=filters.stream().reduce(Predicate::and).orElse(x->true);
または
Predicate<T> pred=filters.stream().reduce(Predicate::or).orElse(x->false);
フィルターの組み合わせ方によって異なります。
orElse
呼び出しで指定された空の述語コレクションのフォールバックがアイデンティティロール(述語のand
ingに対してx->true
が行い、or
ingに対してx->false
が行う)を満たす場合、reduce(x->true, Predicate::and)
またはreduce(x->false, Predicate::or)
を使用して取得することもできますただし、非常に小さなコレクションの場合、フィルターが1つの述部のみを含む場合でもID述部とコレクションの述部を常に結合するため、フィルターの効率はわずかに低下します。対照的に、上記のバリアントreduce(accumulator).orElse(fallback)
は、コレクションのサイズが1
である場合、単一の述語を返します。
このパターンが同様の問題にどのように適用されるかに注意してください:Collection<Consumer<T>>
を使用すると、単一のConsumer<T>
を使用して作成できます
Consumer<T> c=consumers.stream().reduce(Consumer::andThen).orElse(x->{});
等。
ユーザーが1つのフィルター操作で述語のリストを適用したい場合、このような問題を解決することができました。
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;
}
}
これにより、「AND」論理演算子と結合されることに注意してください。 「OR」と組み合わせるには、reduce行を次のようにする必要があります。
Predicate<T> p = Stream.of(predicates).reduce(x -> false, Predicate::or);
これは、この問題を解決する興味深い方法です( 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());
編集:predicatesToIgnoreが述語のリストであるループを処理する方法は次のとおりです。そこから単一の述語predicateToIgnoreを作成します。
Predicate<T> predicateToIgnore = null;
for (Predicate<T> predicate : predicatesToIgnore) {
predicateToIgnore = predicateToIgnore == null ? predicate : predicateToIgnore.or(predicate);
}
次に、この単一の述部でフィルターを実行します。これにより、より良いフィルターが作成されます