创建所有可能的元素组合

时间:2017-09-28 14:53:19

标签: java algorithm performance java-stream

我需要创建某种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,我觉得我错过了一个更简单/更好/更优雅的解决方案。有什么建议? 提前谢谢!

3 个答案:

答案 0 :(得分:1)

是Cartesian产品吗?许多库提供API,例如:Guava中的SetsLists

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

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:1)

规范解决方案是使用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