在Java中8流作为料流(仅使用流)的笛卡尔积在Java中8流作为料流(仅使用流)的笛卡尔积(Car

2019-05-12 02:29发布

我想创建这产生其是多个给定的流(由二进制运算符聚集以在端部是同一类型)的笛卡尔积元件的流的方法。 请注意,这两个参数和结果是流,而不是收藏。

例如,对于两个流{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? 是否可以很容易解决吗?

Answer 1:

在你的榜样传递的流永远不会比通过列表更好:

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种元素的内存不是很大的问题。 其实在大多数的实际情况,他们已经存储在内存中。

为了产生笛卡尔积流,你需要不断的“倒带”的所有流(除了第一个)。 要快退,流应该能够一次又一次地找回原来的数据,无论是缓冲它们以某种方式(你不喜欢的),或从源代码重新抓住他们(colleciton,阵列,文件,网络,随机数,等等。 ),并一次又一次地执行所有的中间业务。 如果您的源和中间业务是缓慢的,那么懒惰的解决方案可能比的缓冲液,慢得多。 如果源是无法再次产生的数据(例如,随机数发生器,它不能产生之前生产的相同号码),您的解决方案将是不正确的。

尽管如此完全懒惰的解决方案是possbile。 只要使用不流,但流供应商:

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);


Answer 2:

stream在消耗flatMap在第二次迭代操作。 所以,你必须创建一个新的流你每次map你的结果。 因此,你必须收集stream提前获得在每个迭代一个新的流。

private static <T> Stream<T> cartesian(BiFunction<T, T, T> aggregator, Stream<T>... streams) {
    Stream<T> result = null;
    for (Stream<T> stream : streams) {
        if (result == null) {
            result = stream;
        } else {
            Collection<T> s = stream.collect(Collectors.toList());
            result = result.flatMap(m -> s.stream().map(n -> aggregator.apply(m, n)));
        }
    }
    return result;
}

或者更短:

private static <T> Stream<T> cartesian(BiFunction<T, T, T> aggregator, Stream<T>... streams) {
    return Arrays.stream(streams).reduce((r, s) -> {
        List<T> collect = s.collect(Collectors.toList());
        return r.flatMap(m -> collect.stream().map(n -> aggregator.apply(m, n)));
    }).orElse(Stream.empty());
}


文章来源: Cartesian product of streams in Java 8 as stream (using streams only)