java - Java 8 中流的笛卡尔积作为流(仅使用流)

标签 java set java-8 java-stream cartesian-product

我想创建一个方法来创建一个元素流,这些元素是多个给定流的笛卡尔积(在末尾由二元运算符聚合为相同类型)。请注意,参数和结果都是流,不是集合。

例如,对于 {A, B}{X, Y} 这两个流,我希望它产生值流 {AX, AY , BX, BY}(简单的连接用于聚合字符串)。到目前为止,我想出了这段代码:

private static <T> Stream<T> cartesian(BinaryOperator<T> aggregator, Stream<T>... streams) {
    Stream<T> result = null;

    for (Stream<T> stream : streams) {
        if (result == null) {
            result = stream;
        } else {
            result = result.flatMap(m -> stream.map(n -> aggregator.apply(m, n)));
        }
    }

    return result;
}

这是我想要的用例:

Stream<String> result = cartesian(
  (a, b) -> a + b, 
  Stream.of("A", "B"), 
  Stream.of("X", "Y")
);

System.out.println(result.collect(Collectors.toList()));

预期结果:AX, AY, BX, BY

另一个例子:

Stream<String> result = cartesian(
  (a, b) -> a + b, 
  Stream.of("A", "B"), 
  Stream.of("K", "L"), 
  Stream.of("X", "Y")
);

预期结果:AKX、AKY、ALX、ALY、BKX、BKY、BLX、BLY

但是,如果我运行代码,我会得到这个错误:

IllegalStateException:流已被操作或关闭

流在哪里消费?通过 flatMap?可以轻松修复吗?

最佳答案

在您的示例中传递流永远不会比传递列表更好:

private static <T> Stream<T> cartesian(BinaryOperator<T> aggregator, List<T>... lists) {
    ...
}

然后像这样使用它:

Stream<String> result = cartesian(
  (a, b) -> a + b, 
  Arrays.asList("A", "B"), 
  Arrays.asList("K", "L"), 
  Arrays.asList("X", "Y")
);

在这两种情况下,您都从可变参数创建隐式数组并将其用作数据源,因此惰性是虚构的。您的数据实际上存储在数组中。

在大多数情况下,生成的笛卡尔积流比输入长得多,因此实际上没有理由使输入惰性化。例如,有五个包含五个元素的列表(总共 25 个),您将得到包含 3125 个元素的结果流。所以在内存中存储 25 个元素不是很大的问题。实际上在大多数实际情况下它们已经存储在内存中。

为了生成笛卡尔积流,您需要不断地“回放”所有流(第一个流除外)。要倒回,流应该能够一次又一次地检索原始数据,或者以某种方式缓冲它们(你不喜欢)或者从源(集合、数组、文件、网络、随机数等)中再次抓取它们。 ) 并一次又一次地执行所有中间操作。如果您的源和中间操作很慢,那么惰性解决方案可能比缓冲解决方案慢得多。如果您的来源无法再次生成数据(例如,随机数生成器无法生成与之前生成的相同的数字),您的解决方案将是不正确的。

然而,完全懒惰的解决方案是可能的。只是不使用流,而是使用流供应商:

private static <T> Stream<T> cartesian(BinaryOperator<T> aggregator,
                                       Supplier<Stream<T>>... streams) {
    return Arrays.stream(streams)
        .reduce((s1, s2) -> 
            () -> s1.get().flatMap(t1 -> s2.get().map(t2 -> aggregator.apply(t1, t2))))
        .orElse(Stream::empty).get();
}

这个解决方案很有趣,因为我们创建并减少供应商流以获取最终供应商并最终调用它。用法:

Stream<String> result = cartesian(
          (a, b) -> a + b, 
          () -> Stream.of("A", "B"), 
          () -> Stream.of("K", "L"), 
          () -> Stream.of("X", "Y")
        );
result.forEach(System.out::println);

关于java - Java 8 中流的笛卡尔积作为流(仅使用流),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32631602/

相关文章:

sql-server - 在 SSIS 中设置字符串变量

java - 基于时间的流媒体

java - 对自定义对象列表进行排序时出现 NullPointerException

java - 如何重定向到不同 EAR 中的登录入口点

c++ - std::set 的 find()

mysql - 如何在 hibernate 中存储本地时间

java - 将 Java 映射枚举键转换为映射字符串键

java - 如何在 Java Swing 中呈现带有复选框的分层字符串列表?

java - onActivityResult 改变我当前的 fragment

c++ - find() 函数对于集合有效吗?