代码之家  ›  专栏  ›  技术社区  ›  voho

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

  •  12
  • voho  · 技术社区  · 9 年前

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

    例如,对于两个流 {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:流已被操作或关闭

    流在哪里消耗?通过 平面地图 ? 它能轻易修复吗?

    3 回复  |  直到 9 年前
        1
  •  12
  •   Tagir Valeev    9 年前

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

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

    在这两种情况下,您都从varargs创建一个隐式数组,并将其用作数据源,因此这种惰性是想象出来的。您的数据实际上存储在数组中。

    在大多数情况下,生成的笛卡尔产品流比输入要长得多,因此实际上没有理由让输入变得懒惰。例如,有五个包含五个元素的列表(总共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);
    
        2
  •  4
  •   Flown    9 年前

    stream flatMap 第二次迭代中的操作。所以每次你都要创建一个新的流 map 您的结果。因此,您必须收集 流动 以在每次迭代中获得新的流。

    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());
    }
    
        3
  •  2
  •   user15675591 user15675591    3 年前

    您可以创建一个返回 List<T> 并且不聚合它们。算法是相同的:在每个步骤中,将第二个流的元素收集到一个列表中,然后将它们附加到第一个流中的元素。

    聚合器在方法之外。

    @SuppressWarnings("unchecked")
    public static <T> Stream<List<T>> cartesianProduct(Stream<T>... streams) {
        // incorrect incoming data
        if (streams == null) return Stream.empty();
        return Arrays.stream(streams)
                // non-null streams
                .filter(Objects::nonNull)
                // represent each list element as SingletonList<Object>
                .map(stream -> stream.map(Collections::singletonList))
                // summation of pairs of inner lists
                .reduce((stream1, stream2) -> {
                    // list of lists from second stream
                    List<List<T>> list2 = stream2.collect(Collectors.toList());
                    // append to the first stream
                    return stream1.flatMap(inner1 -> list2.stream()
                            // combinations of inner lists
                            .map(inner2 -> {
                                List<T> list = new ArrayList<>();
                                list.addAll(inner1);
                                list.addAll(inner2);
                                return list;
                            }));
                }).orElse(Stream.empty());
    }
    
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("A", "B");
        Stream<String> stream2 = Stream.of("K", "L");
        Stream<String> stream3 = Stream.of("X", "Y");
        @SuppressWarnings("unchecked")
        Stream<List<String>> stream4 = cartesianProduct(stream1, stream2, stream3);
        // output
        stream4.map(list -> String.join("", list)).forEach(System.out::println);
    }
    

    String.join 在这种情况下是一种聚合器。

    输出:

    AKX
    AKY
    ALX
    ALY
    BKX
    BKY
    BLX
    BLY
    

    另请参见: Stream of cartesian product of other streams, each element as a List?