【问题标题】:How to iterate all combinations of List<List<T>>如何迭代 List<List<T>> 的所有组合
【发布时间】:2016-02-26 21:00:24
【问题描述】:

我有一个List&lt;List&lt;T&gt;&gt;,两个列表维度的长度各不相同。
Using recursion I can calculate all the combinations

一些示例List&lt;List&lt;T&gt;&gt; 及其组合

[
 [1],
 [2, 3],
 [4, 5, 6]
] 
// [1, 2, 4], [1, 2, 5], [1, 2, 6], [1, 3, 4], [1, 3, 5], [1, 3, 6]

[ 
 [0, 4],
 [3, 4, 1, 2]
]
// [0, 3], [0, 4], [0, 1], [0, 2], [4, 3], [4, 4], [4, 1], [4, 2]

[ 
 ["A", "B", "B"],
 ["C"]
]
// ["A", "C"], ["B", "C"], ["B, "C"]

组合的数量会迅速增长,使用递归会成为内存和性能问题。
如何实现迭代器以在计算组合时对组合进行迭代?

做一些阅读我可能能够使用阶乘或组合数系统,但我不确定在这种情况下我将如何应用它。

【问题讨论】:

  • 您对可能组合的数量感兴趣,还是想要所有可能组合的列表?原因,只需要组合的数量不需要您进行任何递归。它只是 1C1 * 2C1 * 3C1。
  • 可能的组合数量计算起来相当简单,我发布了一个列出每个组合的示例,我有兴趣实现一个迭代器来迭代每个可能的组合,因为它们被计算出来。
  • 我记得在某处使用 Lehmer 代码映射做过一些事情。让我检查一下,然后回到这篇文章。
  • 这个link 就是我所说的。这有帮助吗?
  • @DebosmitRay 我已经阅读了关于阶乘数系统和组合数系统的维基百科条目。它们有点超出我的想象,并且非常注重数学,我不确定它们究竟如何在 Java 中应用或实现。

标签: java iterator combinatorics


【解决方案1】:

您可以实现一个Iterator&lt;List&lt;T&gt;&gt;,它仅将原始元素的引用保存为列表列表和每个子列表的当前位置,并根据需要生成新的组合:

class Combinations<T> implements Iterator<List<T>> {

    final List<List<T>> elements;
    final int[] indices;

    public Combinations(List<List<T>> elements) {
        this.elements = elements;
        this.indices = new int[elements.size()];
    }

    @Override
    public boolean hasNext() {
        // has first index not yet reached max position?
        return indices[0] < elements.get(0).size();
    }

    @Override
    public List<T> next() {
        // get next
        List<T> result = new ArrayList<>(indices.length);
        for (int i = 0; i < indices.length; i++) {
            result.add(elements.get(i).get(indices[i]));
        }
        // increase indices
        for (int i = indices.length - 1; i >= 0; i--) {
            indices[i]++;
            if (indices[i] >= elements.get(i).size() && i > 0) {
                indices[i] %= elements.get(i).size();
            } else {
                break;
            }
        }
        return result;
    }
}

增加索引的部分有点棘手。我想这是阶乘编号系统发挥作用的部分,因为您基本上必须“+1”一个数字(组合索引),其中每个数字都有不同的基数(各个子列表中的元素数)。但是你也可以用一个简单的循环来完成它,而不需要使用任何特殊的库。

我为你的例子试过这个,它似乎工作:

List<List<Integer>> elements = Arrays.asList(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6));
Iterator<List<Integer>> combinations = new Combinations<>(elements);
combinations.forEachRemaining(System.out::println);

输出:

[1, 2, 4]
[1, 2, 5]
[1, 2, 6]
[1, 3, 4]
[1, 3, 5]
[1, 3, 6]

注意:这里使用List&lt;List&lt;T&gt;&gt; 而不是List&lt;Set&lt;T&gt;&gt;,因为您需要对嵌套集合进行索引,但您可以轻松地将其更改为接受List&lt;Collection&lt;T&gt;&gt; 并在构造函数中转换为List

【讨论】:

  • 在您的注释中,我将问题改回 List,在重新检查我解决的问题时,它实际上不是一个内部集合。我也可以将类型更改为 T,因为值类型并不重要。
【解决方案2】:

编辑此答案以适用于 List&lt;List&lt;T&gt;&gt;。 tobias_k 的答案使用iterator 通过迭代列表的索引来获得下一个组合。这做了类似的事情,没有基于迭代器的方法。我遵循的想法是:

     * List 1: [1 2]
     * List 2: [4 5]
     * List 3: [6 7]
     * 
     * Take each element from list 1 and put each element 
     * in a separate list.
     * combinations -> [ [1] [2] ]
     * 
     * Set up something called newCombinations that will contains a list
     * of list of integers
     * Consider [1], then [2]
     * 
     * Now, take the next list [4 5] and iterate over integers
     * [1]
     *  add 4   -> [1 4]
     *      add to newCombinations -> [ [1 4] ]
     *  add 5   -> [1 5]
     *      add to newCombinations -> [ [1 4] [1 5] ]
     * 
     * [2]
     *  add 4   -> [2 4]
     *      add to newCombinations -> [ [1 4] [1 5] [2 4] ]
     *  add 5   -> [2 5]
     *      add to newCombinations -> [ [1 4] [1 5] [2 4] [2 5] ]
     * 
     * point combinations to newCombinations
     * combinations now looks like -> [ [1 4] [1 5] [2 4] [2 5] ]
     * Now, take the next list [6 7] and iterate over integers
     *  ....
     *  6 will go into each of the lists
     *      [ [1 4 6] [1 5 6] [2 4 6] [2 5 6] ]
     *  7 will go into each of the lists
     *      [ [1 4 6] [1 5 6] [2 4 6] [2 5 6] [1 4 7] [1 5 7] [2 4 7] [2 5 7]]

现在是代码。我使用Set 只是为了摆脱任何重复。可以替换为List。一切都应该无缝地工作。 :)

public static <T> Set<List<T>> getCombinations(List<List<T>> lists) {
    Set<List<T>> combinations = new HashSet<List<T>>();
    Set<List<T>> newCombinations;

    int index = 0;

    // extract each of the integers in the first list
    // and add each to ints as a new list
    for(T i: lists.get(0)) {
        List<T> newList = new ArrayList<T>();
        newList.add(i);
        combinations.add(newList);
    }
    index++;
    while(index < lists.size()) {
        List<T> nextList = lists.get(index);
        newCombinations = new HashSet<List<T>>();
        for(List<T> first: combinations) {
            for(T second: nextList) {
                List<T> newList = new ArrayList<T>();
                newList.addAll(first);
                newList.add(second);
                newCombinations.add(newList);
            }
        }
        combinations = newCombinations;

        index++;
    }

    return combinations;
}

一个小测试块..

public static void main(String[] args) {
    List<Integer> l1 = Arrays.asList(1,2,3);
    List<Integer> l2 = Arrays.asList(4,5);
    List<Integer> l3 = Arrays.asList(6,7);

    List<List<Integer>> lists = new ArrayList<List<Integer>>();
    lists.add(l1);
    lists.add(l2);
    lists.add(l3);

    Set<List<Integer>> combs = getCombinations(lists);
    for(List<Integer> list : combs) {
        System.out.println(list.toString());
    }

}

【讨论】:

  • 谢谢,这个答案的列表变体更符合我更新的问题。集合变体的一个特点是顺序很奇怪。也感谢您为此花费的所有时间。
  • 是的,这是因为使用 HashSet 时的散列。我提到将 Set 更改为 List,一切都应该是完美的。
猜你喜欢
  • 2013-10-31
  • 1970-01-01
  • 2021-05-27
  • 2012-01-11
  • 2014-06-25
  • 2021-05-26
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
相关资源
最近更新 更多