当前位置: 首页 > 面试题库 >

如何使用Java 8流制作笛卡尔产品?

仲孙兴平
2023-03-14
问题内容

我具有以下集合类型:

Map<String, Collection<String>> map;

我想map.size()为每个Key从集合中的单个值创建每个的唯一组合。

例如,假设地图如下所示:

A, {a1, a2, a3, ..., an}
B, {b1, b2, b3, ..., bn}
C, {c1, c2, c3, ..., cn}

我想得到的List<Set<String>>结果看起来像是一个结果(排序并不重要,它只需要是由所有可能的组合组成的“完整”结果)即可:

{a1, b1, c1},
{a1, b1, c2},
{a1, b1, c3},
{a1, b2, c1},
{a1, b2, c2},
{a1, b2, c3},
...
{a2, b1, c1},
{a2, b1, c2},
...
{a3, b1, c1},
{a3, b1, c2},
...
{an, bn, cn}

这基本上是一个计数问题,但是我想看看使用Java 8流是否可以解决。


问题答案:

您可以使用递归flatMap链解决此问题。

首先,当我们需要在映射值之间来回移动时,最好将它们复制到ArrayList(这不是深度复制,在您的情况下,它ArrayList仅包含3个元素,因此额外的内存使用率很低)。

其次,为维护先前访问的元素的前缀,让我们创建一个助手不可变Prefix类:

private static class Prefix<T> {
    final T value;
    final Prefix<T> parent;

    Prefix(Prefix<T> parent, T value) {
        this.parent = parent;
        this.value = value;
    }

    // put the whole prefix into given collection
    <C extends Collection<T>> C addTo(C collection) {
        if (parent != null)
            parent.addTo(collection);
        collection.add(value);
        return collection;
    }
}

这是一个非常简单的不可变链表,可以像这样使用:

List<String> list = new Prefix<>(new Prefix<>(new Prefix<>(null, "a"), "b"), "c")
                          .addTo(new ArrayList<>()); // [a, b, c];

接下来,让我们创建链接flatMaps的内部方法:

private static <T, C extends Collection<T>> Stream<C> comb(
        List<? extends Collection<T>> values, int offset, Prefix<T> prefix,
        Supplier<C> supplier) {
    if (offset == values.size() - 1)
        return values.get(offset).stream()
                     .map(e -> new Prefix<>(prefix, e).addTo(supplier.get()));
    return values.get(offset).stream()
            .flatMap(e -> comb(values, offset + 1, new Prefix<>(prefix, e), supplier));
}

看起来像递归,但更复杂:它不会直接调用自身,而是传递了lambda来调用外部方法。参数:

  • values:List原始值(new ArrayList<>(map.values)根据您的情况)。
  • offset:此列表中的当前偏移量
  • 前缀:长度的偏移的当前前缀(或null如果offset == 0)。它包含当前从集合中选择的元素list.get(0)list.get(1)直到list.get(offset-1)
  • 供应商:创建结果集合的工厂方法。

到达值列表(offset == values.size() - 1)的末尾时,我们使用供应商将最后一个集合的元素从值映射到最终组合。否则,我们flatMap对每个中间元素使用which来放大前缀,并comb为下一个偏移量再次调用该方法。

最后是使用此功能的公共方法:

public static <T, C extends Collection<T>> Stream<C> ofCombinations(
        Collection<? extends Collection<T>> values, Supplier<C> supplier) {
    if (values.isEmpty())
        return Stream.empty();
    return comb(new ArrayList<>(values), 0, null, supplier);
}

一个用法示例:

Map<String, Collection<String>> map = new LinkedHashMap<>(); // to preserve the order
map.put("A", Arrays.asList("a1", "a2", "a3", "a4"));
map.put("B", Arrays.asList("b1", "b2", "b3"));
map.put("C", Arrays.asList("c1", "c2"));

ofCombinations(map.values(), LinkedHashSet::new).forEach(System.out::println);

我们LinkedHashSet再次收集单个组合以保留订单。您可以改用其他任何集合(例如ArrayList::new)。



 类似资料:
  • 我有以下收藏类型: 我希望根据集合中每个键的单个值为每个创建唯一的组合。

  • 例如,对于{A,B}和{X,Y}这两个流,我希望它生成值流{AX,AY,BX,BY}(简单的串联用于聚合字符串)。到目前为止,我已经想出了这段代码: 这是我想要的用例: 预期结果:。 溪流消耗在哪里?按平面地图?很容易修好吗?

  • 假设我有一个带有键和时间戳的流。我想在每个窗口(滑动窗口)中创建这些关键点的笛卡尔积。如果我有键1,2,3,4,并且我已经将并行度设置为2,我想用以下方式对它们进行“分组”: 我想处理每个窗口中每个组的元素。因此,假设上述元素(1,2,3,4)在同一个窗口中,基于它们的时间戳。 以最简单的形式,我的问题是:给定每个滑动窗口中的一些元素(可能包含多个键),我想创建这些键的组合,如上面的示例所示,并对

  • 的结果将是二维数组: 我试图做的是使用流在Java中编写这个笛卡尔乘积函数。 到目前为止,我有以下Java版本: 我对问题的猜测是: 我需要在某个地方使用收集器(可能在之后) 标识的数据类型错误

  • 问题内容: 我想创建一个方法,该方法创建元素流,这些元素流是多个给定流的笛卡尔积(由二元运算符最后汇总为相同类型)。请注意,参数和结果都是流, 而不是 集合。 例如,对于 {A,B} 和 {X,Y}的 两个流,我希望它产生值 {AX,AY,BX,BY}的流 (简单串联用于聚集字符串)。到目前为止,我想出了以下代码: 这是我想要的用例: 预期结果:。 另一个例子: 预期结果:。 但是,如果我运行代码

  • 现在我只能实现两个集合的笛卡尔积,下面是代码: 这段代码在IntelliJ中运行良好,但在Eclipse(两者的编译器遵从级别均为1.8)中就不行了: 下面是pair.java: 如何修复这个错误? 有没有一个优雅的方法来实现几个收藏的笛卡尔产品?(假设我们有类)