【问题标题】:Calculate all permutations of a collection in parallel并行计算集合的所有排列
【发布时间】:2016-12-08 13:49:10
【问题描述】:


我需要计算一个集合的所有排列,我有一个代码,但问题是它是线性的并且需要很多时间。

public static <E> Set<Set<E>> getAllCombinations(Collection<E> inputSet) {
    List<E> input = new ArrayList<>(inputSet);
    Set<Set<E>> ret = new HashSet<>();
    int len = inputSet.size();
    // run over all numbers between 1 and 2^length (one number per subset). each bit represents an object
    // include the object in the set if the corresponding bit is 1
    for (int i = (1 << len) - 1; i > 0; i--) {
        Set<E> comb = new HashSet<>();
        for (int j = 0; j < len; j++) {
            if ((i & 1 << j) != 0) {
                comb.add(input.get(j));
            }
        }
        ret.add(comb);
    }
    return ret;
}

我正在尝试使计算并行运行。

我虽然可以选择使用递归来编写逻辑,然后并行执行递归调用,但我不确定该怎么做。

不胜感激。

【问题讨论】:

  • 我不确定并行计算是否会对您有所帮助,除非您拥有大型多核处理器。但是,如果您可以使用 Java8 Stream 作为返回值而不是 Set,这会有所帮助,因为 Sreams 会按需创建下一个元素。
  • Streamplify 库为排列、组合、笛卡尔积等提供并行流。查看实现或直接在您的代码中使用它。

标签: java recursion parallel-processing combinations java-stream


【解决方案1】:

实际上,没有必要使用递归,这可能会适得其反。由于每个组合的创建都可以独立于其他组合执行,因此可以使用并行 Streams 来完成。请注意,您甚至不需要手动执行位操作:

public static <E> Set<Set<E>> getAllCombinations(Collection<E> inputSet) {
    // use inputSet.stream().distinct().collect(Collectors.toList());
    // to get only distinct combinations
    //  (in case source contains duplicates, i.e. is not a Set)
    List<E> input = new ArrayList<>(inputSet);
    final int size = input.size();
    // sort out input that is too large. In fact, even lower numbers might
    // be way too large. But using <63 bits allows to use long values
    if(size>=63) throw new OutOfMemoryError("not enough memory for "
        +BigInteger.ONE.shiftLeft(input.size()).subtract(BigInteger.ONE)+" permutations");

    // the actual operation is quite compact when using the Stream API
    return LongStream.range(1, 1L<<size) /* .parallel() */
        .mapToObj(l -> BitSet.valueOf(new long[] {l}).stream()
            .mapToObj(input::get).collect(Collectors.toSet()))
        .collect(Collectors.toSet());
}

内部流操作,即对位进行迭代,太小而无法从并行操作中受益,尤其是因为它必须将结果合并到单个 Set 中。但如果要生成的组合数量足够多,并行运行外部流将已经使用所有 CPU 内核。

替代方法是不使用并行流,而是返回Stream&lt;Set&lt;E&gt;&gt; 本身而不是收集到Set&lt;Set&lt;E&gt;&gt; 中,以允许调用者直接链接消费操作。

顺便说一下,散列整个Set(或很多)可能非常昂贵,因此最终合并步骤的成本可能会主导性能。相反,返回 List&lt;Set&lt;E&gt;&gt; 可以显着提高性能。这同样适用于返回 Stream&lt;Set&lt;E&gt;&gt; 而不收集组合的替代方案,因为这也适用于不散列 Sets 的情况。

【讨论】:

  • 我花了一段时间才弄到,但它非常好。
  • if(size>63)不应该是与64的比较吗?这不像我们关心这里的标志。另外,我有点不同意为此目的抛出 OutOfMemory。
  • @Eugene:哈哈,实际上应该是62(或&gt;=63),因为1L&lt;&lt;63 已经是负数了。位提取无关紧要,但LongStream.range 确实关心符号。尝试支持≥63 元素会使实现复杂化而没有任何好处,因为我们仍然无法将组合保存在内存中(这就是为什么OutOfMemoryError 是合适的)。请注意,即使每个组合都像 int 一样小,64 位 JVM 也没有足够的内存来保存 2⁶² 组合原则上。但我们谈论的是Sets。如果我们之前没有拒绝,OOME 就会发生。
  • @Eugene:如果你返回一个Stream,你可以走this answer的道路,它只会欺骗和处理前2⁶²可能的组合。因为即使每个组合都在一纳秒内处理,处理所有这些仍然需要 146 年(实际上会慢得多),没有人会注意到。 (你可以做一个正确的实现来处理更多的情况,例如使用BigInteger,但你甚至无法在你的一生中测试它)。
  • 我认为 LOL 是关于“谁在乎?”的部分。超过 30 个(或任何数字),这将需要很长时间才能完成,对吧?其他一切我都同意。非常感谢后续工作。
猜你喜欢
  • 2021-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-04
  • 2018-06-10
  • 2019-10-11
相关资源
最近更新 更多