使用Java Streams的笛卡尔积

时间:2016-07-27 21:48:21

标签: java java-stream

我在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)的结果将是2D数组:

[['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<String>Stream<Stream<String>>

不兼容

我猜错了什么:

  • 我需要在某个地方使用收藏家(可能在Stream.concat之后)
  • 身份的数据类型错误

1 个答案:

答案 0 :(得分:2)

这可以通过一些函数式编程魔术实现。这是接受Collection<Collection<Collection<T>>>并生成Stream<Collection<T>>的方法:

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]

当然,如果您想要返回.forEach()而不是List,则可以将结果收集到Collection<Collection<T>>,但是返回Stream对我来说似乎更灵活。< / p>

一点解释:

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