如何迭代 List<List<T>> 的所有组合
How to iterate all combinations of List<List<T>>
我有一个 List<List<T>>
两个列表维度的长度各不相同。
Using recursion I can calculate all the combinations
一些示例List<List<T>>
及其组合
[
[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"]
组合的数量会迅速增长,使用递归会成为内存和性能问题。
如何实现迭代器以在计算组合时对其进行迭代?
通过阅读,我也许可以使用阶乘或组合数字系统,但我不确定在这种情况下我将如何应用它。
您可以实现一个 Iterator<List<T>>
,它仅将对原始元素的引用保存为列表列表和每个子列表的当前位置,并根据需要生成新组合:
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<List<T>>
而不是 List<Set<T>>
,因为您需要对嵌套集合进行索引,但您可以轻松地将其更改为接受 List<Collection<T>>
并转换为 List
在构造函数中。
编辑此答案以供 List<List<T>>
使用。 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());
}
}
我有一个 List<List<T>>
两个列表维度的长度各不相同。
Using recursion I can calculate all the combinations
一些示例List<List<T>>
及其组合
[
[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"]
组合的数量会迅速增长,使用递归会成为内存和性能问题。
如何实现迭代器以在计算组合时对其进行迭代?
通过阅读,我也许可以使用阶乘或组合数字系统,但我不确定在这种情况下我将如何应用它。
您可以实现一个 Iterator<List<T>>
,它仅将对原始元素的引用保存为列表列表和每个子列表的当前位置,并根据需要生成新组合:
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<List<T>>
而不是 List<Set<T>>
,因为您需要对嵌套集合进行索引,但您可以轻松地将其更改为接受 List<Collection<T>>
并转换为 List
在构造函数中。
编辑此答案以供 List<List<T>>
使用。 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());
}
}