【问题标题】:Create all possible combinations of elements创建所有可能的元素组合
【发布时间】:2017-09-28 14:53:19
【问题描述】:

我需要创建某种 Key 的所有可能组合,它由 X(在我的例子中为 8)组成,同样重要的元素。所以我想出了这样的代码:

final LinkedList<Key> keys = new LinkedList();

firstElementCreator.getApplicableElements() // All creators return a Set of elements
          .forEach( first -> secondElementCreator.getApplicableElements()
           .forEach( second -> thirdElementCreator.getApplicableElements()
            // ... more creators
           .forEach( X -> keys.add( new Key( first, second, third, ..., X ) ) ) ) ) ) ) ) );

return keys;

它正在工作,但是有 X 嵌套 forEach,我觉得我错过了一个更简单/更好/更优雅的解决方案。有什么建议么? 提前致谢!

【问题讨论】:

  • 当你需要任意数量的循环时,递归几乎总是答案。

标签: java algorithm performance java-stream


【解决方案1】:

是笛卡尔积吗?许多库都提供了 API,例如:Sets 和 Guava 中的Lists

List<ApplicableElements> elementsList = Lists.newArrayList(firstElementCreator, secondElementCreator...).stream()
        .map(c -> c.getApplicableElements()).collect(toList());

List<Key> keys = Lists.cartesianProduct(elementsList).stream()
        .map(l -> new Key(l.get(0), l.get(1), l.get(2), l.get(3), l.get(4), l.get(5), l.get(6), l.get(7))).collect(toList());

【讨论】:

  • 这是我一直在寻找的答案。不过,这个解决方案有一个缺点:我的每个元素都有不同的类型(现在我看到我没有提到),所以类型转换是必要的。也许可以创建一些通用接口,我会考虑的。谢谢。
【解决方案2】:

由于输入集的数量是固定的(它必须与 Key 构造函数中的参数数量相匹配),因此您的解决方案实际上还不错。

不过,如果没有 lambda,它会更高效、更容易阅读,例如:

for (Element first : firstElementCreator.getApplicableElements()) {
    for (Element second : secondElementCreator.getApplicableElements()) {
        for (Element third : thirdElementCreator.getApplicableElements()) {
            keys.add(new Key(first, second, third));
        }
    }
}

【讨论】:

  • 你说得对,流并不总是最好的解决方案,这肯定看起来更好,谢谢。
【解决方案3】:

规范的解决方案是使用flatMap。然而,棘手的部分是从多个输入级别创建Key 对象。

直接的方法是在最里面的函数中进行评估,其中每个值都在范围内

final List<Key> keys = firstElementCreator.getApplicableElements().stream()
  .flatMap(first -> secondElementCreator.getApplicableElements().stream()
    .flatMap(second -> thirdElementCreator.getApplicableElements().stream()
      // ... more creators
      .map( X -> new Key( first, second, third, ..., X ) ) ) )
  .collect(Collectors.toList());

但这很快就会因为深度嵌套而变得不切实际

没有深度嵌套的解决方案需要元素来保存中间复合值。例如。如果我们将Key 定义为

class Key {
    String[] data;
    Key(String... arg) {
        data=arg;
    }
    public Key add(String next) {
        int pos = data.length;
        String[] newData=Arrays.copyOf(data, pos+1);
        newData[pos]=next;
        return new Key(newData);
    }

    @Override
    public String toString() {
        return "Key("+Arrays.toString(data)+')';
    }
}

(假设String为元素类型),我们可以使用

final List<Key> keys =
    firstElementCreator.getApplicableElements().stream().map(Key::new)
      .flatMap(e -> secondElementCreator.getApplicableElements().stream().map(e::add))
      .flatMap(e -> thirdElementCreator.getApplicableElements().stream().map(e::add))
      // ... more creators
      .collect(Collectors.toList());

请注意,这些 flatMap 步骤现在位于同一级别,即不再嵌套。此外,所有这些步骤都是相同的,只是实际创建者不同,这导致通用解决方案支持任意数量的Creator 实例。

List<Key> keys = Stream.of(firstElementCreator, secondElementCreator, thirdElementCreator
                           /* , and, some, more, if you like */)
    .map(creator -> (Function<Key,Stream<Key>>)
                    key -> creator.getApplicableElements().stream().map(key::add))
    .reduce(Stream::of, (f1,f2) -> key -> f1.apply(key).flatMap(f2))
    .apply(new Key())
    .collect(Collectors.toList());

在这里,每个创建者都映射到与前一个解决方案相同的流生成函数,然后将所有函数都简化为一个函数,将每个函数与flatMap 步骤组合到下一个,最后执行生成的函数获取流,然后将其收集到List

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-18
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多