[英]Stream a collection and collect into multiple results depending on independent predicates
我盯着一些命令式代码,我试图将其转换为纯函数式风格。 基本上,在inputSet
有一个迭代 for 循环,其中我检查 3 个谓词并根据哪个谓词匹配来填充 3 个outputSets
。 输出集可以重叠。 如何使用 Java 8 流/映射/过滤器/等以纯函数方式执行此操作?
最简单的解决方案(除了让一切保持原样更容易)是创建三个单独的流:
Set<MyObj> set1 = inputSet.stream().filter(pred1).collect(Collectors.toSet());
Set<MyObj> set2 = inputSet.stream().filter(pred2).collect(Collectors.toSet());
Set<MyObj> set3 = inputSet.stream().filter(pred3).collect(Collectors.toSet());
如果您有谓词列表,则可以创建相应的集合列表作为结果:
List<Predicate<MyObj>> predicates = Arrays.asList(pred1, pred2, pred3);
List<Set<MyObj>> result = predicates.stream()
.map(pred -> inputSet.stream().filter(pred).collect(Collectors.toSet()))
.collect(Collectors.toList());
这里结果列表中的第一个集合对应于第一个谓词,依此类推。
如果您真的想一次性处理您的输入(无论出于何种原因),您可以为此编写一个特殊的收集器。 这是一个非常普遍的:
public static <T, A, R> Collector<T, ?, List<R>> multiClassify(
List<Predicate<T>> predicates, Collector<? super T, A, R> downstream) {
Supplier<A> dsSupplier = downstream.supplier();
BiConsumer<A, ? super T> dsAccumulator = downstream.accumulator();
BinaryOperator<A> dsCombiner = downstream.combiner();
Supplier<List<A>> supplier = () -> Stream.generate(dsSupplier)
.limit(predicates.size()).collect(Collectors.toList());
BiConsumer<List<A>, T> accumulator = (list, t) -> IntStream
.range(0, predicates.size()).filter(i -> predicates.get(i).test(t))
.forEach(i -> dsAccumulator.accept(list.get(i), t));
BinaryOperator<List<A>> combiner = (l1, l2) -> IntStream.range(0, predicates.size())
.mapToObj(i -> dsCombiner.apply(l1.get(i), l2.get(i)))
.collect(Collectors.toList());
Characteristics[] dsCharacteristics = downstream.characteristics().toArray(
new Characteristics[0]);
if (downstream.characteristics().contains(Characteristics.IDENTITY_FINISH)) {
@SuppressWarnings("unchecked")
Collector<T, ?, List<R>> result = (Collector<T, ?, List<R>>) (Collector<T, ?, ?>)
Collector.of(supplier, accumulator, combiner, dsCharacteristics);
return result;
}
Function<A, R> dsFinisher = downstream.finisher();
Function<List<A>, List<R>> finisher = l -> l.stream().map(dsFinisher)
.collect(Collectors.toList());
return Collector.of(supplier, accumulator, combiner, finisher, dsCharacteristics);
}
它需要一个谓词列表并返回每个谓词的下游收集器结果列表。 用法示例:
List<String> input = asList("abc", "ade", "bcd", "cc", "cdac");
List<Predicate<String>> preds = asList(
s -> s.length() == 3,
s -> s.startsWith("a"),
s -> s.endsWith("c"));
List<Set<String>> result = input.stream().collect(multiClassify(preds, Collectors.toSet()));
// [[bcd, abc, ade], [abc, ade], [cc, abc, cdac]]
另一种方法是使用Consumer.andThen(anotherConsumer)
方法创建一个由按顺序执行的内部消费者组成的组合消费者。 这些内部消费者中的每一个都会测试每个谓词并根据它们是否匹配对元素进行分类。
public static <T> Consumer<T> classify(Predicate<T> predicate, Consumer<T> action) {
return elem -> Optional.ofNullable(elem)
.filter(predicate)
.ifPresent(action);
}
此实用程序方法返回一个消费者,该消费者将对正在消费的元素执行给定的操作,只要谓词对该元素返回true
。
测试:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
Set<Integer> set1 = new LinkedHashSet<>();
Set<Integer> set2 = new LinkedHashSet<>();
Set<Integer> set3 = new LinkedHashSet<>();
// Here's the composed consumer, made of inner consumers
Consumer<Integer> multiClassifier = classify(n -> n % 2 == 0, set1::add)
.andThen(classify(n -> n % 3 == 0, set2::add))
.andThen(classify(n -> n % 5 == 0, set3::add));
// Here the stream is consumed by the composed consumer
stream.forEach(multiClassifier);
每个内部消费者都是用上面定义的实用程序方法创建的,它接收一个独立的谓词,当匹配时,会将流的元素添加到给定的集合中,即如果流的元素是 3 的倍数,它将是添加到set2
。
最后,流被这个组合消费者消费,因此流被独立的谓词分类:
System.out.println(set1); // [2, 4, 6, 8, 10, 12]
System.out.println(set2); // [3, 6, 9, 12]
System.out.println(set3); // [5, 10]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.