【问题标题】:Cartesian product using Java Streams使用 Java 流的笛卡尔积
【发布时间】:2016-07-27 21:48:20
【问题描述】:

我在 JavaScript 中有一个笛卡尔积函数:

function cartesianProduct(arr) {
    return arr.reduce(function(a,b) {
        return a.map(function(x) {
            return b.map(function(y) {
                return x.concat(y);
            });
        }).reduce(function(a,b) { return a.concat(b); }, []);
    }, [[]]);
}

如果我有一个 3D 数组:

var data = [[['D']], [['E'],['L','M','N']]];

cartesianProduct(data) 的结果将是二维数组:

[['D','E'], ['D','L','M','N']]

我正在尝试使用 Streams 在 Java 中编写这个笛卡尔积函数。

到目前为止,我在 Java 中有以下内容:

public Collection<Collection<String>> cartesianProduct(Collection<Collection<Collection<String>>> arr) {

    return arr.stream().reduce(new ArrayList<Collection<String>>(), (a, b) -> {
        return a.stream().map(x -> {
            return b.stream().map(y -> {
                return Stream.concat(x.stream(), y.stream());
            });
        }).reduce(new ArrayList<String>(), (c, d) -> {
            return Stream.concat(c, d);
        });
    });
}

我有一个类型检查错误指出:

ArrayList&lt;String&gt; 与Stream&lt;Stream&lt;String&gt;&gt; 不兼容

我对问题的猜测:

  • 我需要在某处使用收集器(可能在Stream.concat 之后)
  • 身份的数据类型错误

【问题讨论】:

  • 这和叉积有什么关系?
  • 我不认为 Stream 是正确的模型。 Stream 获取单个列表/集合/池/任何元素的元素并对其进行处理。但是您试图从两个独立的实体中获取元素并以特定方式组合它们。您可以使用 lambda 表达式来表示所有 JavaScript 闭包,但我认为 Stream 不是正确的工具。
  • @michaelsnowden 尝试使用 Java 流实现交叉积函数。您是否有任何实际价值可以添加到讨论中,或者您仍然感到困惑?
  • @Bobulous 上周我对 Streams 了解得越多,我就越意识到这一点。我认为我将 Streams 视为函数式编程中的闭包。

标签: java java-stream


【解决方案1】:

这可以通过一点函数式编程魔法来实现。这是接受Collection&lt;Collection&lt;Collection&lt;T&gt;&gt;&gt; 并产生Stream&lt;Collection&lt;T&gt;&gt; 的方法:

static <T> Stream<Collection<T>> cartesianProduct(Collection<Collection<Collection<T>>> arr)
{
    return arr.stream()
        .<Supplier<Stream<Collection<T>>>> map(c -> c::stream)
        .reduce((s1, s2) -> () -> s1.get().flatMap(
                a -> s2.get().map(b -> Stream.concat(a.stream(), b.stream())
                        .collect(Collectors.toList()))))
        .orElseGet(() -> () -> Stream.<Collection<T>>of(Collections.emptyList()))
        .get();
}

使用示例:

cartesianProduct(
    Arrays.asList(Arrays.asList(Arrays.asList("D")),
        Arrays.asList(Arrays.asList("E"), Arrays.asList("L", "M", "N"))))
            .forEach(System.out::println);

输出:

[D, E]
[D, L, M, N]

当然,如果您想返回 Collection&lt;Collection&lt;T&gt;&gt;,而不是 .forEach(),您可以将结果收集到 List,但返回 Stream 对我来说似乎更灵活。

一点解释:

在这里,我们通过map(c -&gt; c::stream) 创建一个流供应商流。该流的每个功能都可以按需产生相应集合元素的流。我们这样做是因为流一次关闭(否则拥有流就足够了)。之后,我们减少了这个供应商流,为每一对创建一个新的供应商,它 flatMaps 两个流并将它们的元素映射到连接列表。 orElseGet 部分是处理空输入所必需的。最后一个.get() 只是调用最终的流供应商来获取结果流。

【讨论】:

    猜你喜欢
    • 2015-12-14
    • 2013-12-25
    • 1970-01-01
    • 2015-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多