【问题标题】:Generate all combinations from multiple lists从多个列表生成所有组合
【发布时间】:2013-06-16 01:54:27
【问题描述】:

给定未知数量的列表,每个列表的长度未知,我需要生成一个包含所有可能唯一组合的奇异列表。 例如,给定以下列表:

X: [A, B, C] 
Y: [W, X, Y, Z]

那么我应该可以生成12个组合:

[AW, AX, AY, AZ, BW, BX, BY, BZ, CW, CX, CY, CZ]

如果添加了 3 个元素的第三个列表,我将有 36 种组合,依此类推。

关于如何在 Java 中做到这一点的任何想法?
(伪代码也可以)

【问题讨论】:

  • 不是的,我在工作中出现了短暂的脑筋急转弯,所以我没有花很多时间自己解决这个问题,而是来到这里 :)
  • 如果你谈论所有可能的独特组合,不应该有更多吗?例如,您没有在最终列表中报告的唯一组合是 [A].. 所以它应该是 [A, B, C, W, X, Y, Z, AW, AX, AY, AZ, BW, BX、BY、BZ、CW、CX、CY、CZ]

标签: java list algorithm combinations cartesian-product


【解决方案1】:

你需要递归:

假设您的所有列表都在lists 中,这是一个列表列表。让result 成为您所需排列的列表。你可以这样实现它:

void generatePermutations(List<List<Character>> lists, List<String> result, int depth, String current) {
    if (depth == lists.size()) {
        result.add(current);
        return;
    }

    for (int i = 0; i < lists.get(depth).size(); i++) {
        generatePermutations(lists, result, depth + 1, current + lists.get(depth).get(i));
    }
}

最终的调用会是这样的:

generatePermutations(lists, result, 0, "");

【讨论】:

  • 这主要是javaish。我删除了 String.add 和 String.removeLastCharacter 但这样做稍微改变了你的逻辑(希望更好)。随时恢复。
  • 经过一些编辑以便它可以与双打列表一起使用(我在我的问题中使用了字符串,因为我认为它更容易解释),这非常有效,谢谢!
  • @armen tsirunyan 是否很难修改它以生成列表结果列表,例如:[[A,W],[A,X],[A,Y]]?
  • @turbo2oh:这需要对程序进行一些简单的修改,只需在任意位置添加逗号和括号即可。
  • 它正在测试中:有 2、3 和 4 个字符串列表,它工作得很好......非常感谢!
【解决方案2】:

此操作称为cartesian product。 Guava 为此提供了一个实用函数:Lists.cartesianProduct

【讨论】:

  • 使用库总是比重新发明轮子好。
  • 这很有帮助。
【解决方案3】:

这个话题派上了用场。我已经完全用 Java 重写了以前的解决方案,并且更加用户友好。此外,我使用集合和泛型以获得更大的灵活性:

/**
 * Combines several collections of elements and create permutations of all of them, taking one element from each
 * collection, and keeping the same order in resultant lists as the one in original list of collections.
 * 
 * <ul>Example
 * <li>Input  = { {a,b,c} , {1,2,3,4} }</li>
 * <li>Output = { {a,1} , {a,2} , {a,3} , {a,4} , {b,1} , {b,2} , {b,3} , {b,4} , {c,1} , {c,2} , {c,3} , {c,4} }</li>
 * </ul>
 * 
 * @param collections Original list of collections which elements have to be combined.
 * @return Resultant collection of lists with all permutations of original list.
 */
public static <T> Collection<List<T>> permutations(List<Collection<T>> collections) {
  if (collections == null || collections.isEmpty()) {
    return Collections.emptyList();
  } else {
    Collection<List<T>> res = Lists.newLinkedList();
    permutationsImpl(collections, res, 0, new LinkedList<T>());
    return res;
  }
}

/** Recursive implementation for {@link #permutations(List, Collection)} */
private static <T> void permutationsImpl(List<Collection<T>> ori, Collection<List<T>> res, int d, List<T> current) {
  // if depth equals number of original collections, final reached, add and return
  if (d == ori.size()) {
    res.add(current);
    return;
  }

  // iterate from current collection and copy 'current' element N times, one for each element
  Collection<T> currentCollection = ori.get(d);
  for (T element : currentCollection) {
    List<T> copy = Lists.newLinkedList(current);
    copy.add(element);
    permutationsImpl(ori, res, d + 1, copy);
  }
}

我正在使用 guava 库来创建集合。

【讨论】:

  • 这段代码对我帮助很大。我很感激,但我能知道你为什么使用 Lists.newLinkedList 而不是 List copy = new LinkedList();这个版本是否更有效。我仍然看到我上面写的方式更常用。
  • 钻石运算符在我当时使用的JDK版本中是不可用的,所以我使用那些工厂类(例如Lists、Sets或Maps)只是为了代码的方便和清晰。它们并没有更高效或类似的东西。
【解决方案4】:

添加基于迭代器的答案以适用于列表的通用列表List&lt;List&lt;T&gt;&gt;,扩展了 Ruslan Ostafiichuk 的答案的想法。我遵循的想法是:

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

【讨论】:

    【解决方案5】:

    没有递归唯一组合:

    String sArray[] = new String[]{"A", "A", "B", "C"};
    //convert array to list
    List<String> list1 = Arrays.asList(sArray);
    List<String> list2 = Arrays.asList(sArray);
    List<String> list3 = Arrays.asList(sArray);
    
    LinkedList<List<String>> lists = new LinkedList<List<String>>();
    
    lists.add(list1);
    lists.add(list2);
    lists.add(list3);
    
    Set<String> combinations = new TreeSet<String>();
    Set<String> newCombinations;
    
    for (String s : lists.removeFirst())
        combinations.add(s);
    
    while (!lists.isEmpty()) {
        List<String> next = lists.removeFirst();
        newCombinations = new TreeSet<String>();
        for (String s1 : combinations)
            for (String s2 : next)
                newCombinations.add(s1 + s2);
    
        combinations = newCombinations;
    }
    for (String s : combinations)
        System.out.print(s + " ");
    

    【讨论】:

    • 我认为您不希望 combinationsnewCombinations 成为 Sets。他没有指定任何类型的唯一性限制。我会把它们都设为Lists,然后我相信它会起作用。
    • 他说“所有可能的独特组合”。使用列表而不是集合后,在我的情况下,结果将是“AAA,AAA,ABA ...”{“A”,“A”,“B”,“C”}。
    • 啊,你是对的。他的例子让我觉得他不在乎,因为他说“如果我们添加第三个长度为 3 的列表,那么将有 36 个”,如果你关心唯一性,这不一定是真的。哦,好吧,我已经 +1 了
    【解决方案6】:

    您需要实现的操作称为笛卡尔积。 更多详情见https://en.wikipedia.org/wiki/Cartesian_product

    我建议使用我的开源库,它可以完全满足您的需求: https://github.com/SurpSG/Kombi

    有示例如何使用它: https://github.com/SurpSG/Kombi#usage-for-lists-1

    注意: 该库是为高性能目的而设计的。您可以观察基准结果here

    该库为您提供了相当不错的吞吐量和恒定的内存使用率

    【讨论】:

      【解决方案7】:

      使用此处其他一些答案提供的嵌套循环解决方案来组合两个列表。

      当您有两个以上的列表时,

      1. 将前两个合并到一个新列表中。
      2. 将结果列表与下一个输入列表合并。
      3. 重复。

      【讨论】:

        【解决方案8】:

        像往常一样迟到,但这里有一个很好解释的使用数组的示例。它可以很容易地更改为列表。我的用例需要多个数组的所有唯一组合,按字典顺序排列。

        我发布它是因为这里的答案都没有给出明确的算法,我无法忍受递归。毕竟我们不是在 stackoverflow 上吗?

        String[][] combinations = new String[][] {
                         new String[] { "0", "1" },
                         new String[] { "0", "1" },
                         new String[] { "0", "1" },
                         new String[] { "0", "1" } };
        
            int[] indices = new int[combinations.length];
            
            int currentIndex = indices.length - 1;
            outerProcess: while (true) {
        
                for (int i = 0; i < combinations.length; i++) {
                    System.out.print(combinations[i][indices[i]]);
                }
                System.out.println();
        
                while (true) {
                    // Increase current index
                    indices[currentIndex]++;
                    // If index too big, set itself and everything right of it to 0 and move left
                    if (indices[currentIndex] >= combinations[currentIndex].length) {
                        for (int j = currentIndex; j < indices.length; j++) {
                            indices[j] = 0;
                        }
                        currentIndex--;
                    } else {
                        // If index is allowed, move as far right as possible and process next
                        // combination
                        while (currentIndex < indices.length - 1) {
                            currentIndex++;
                        }
                        break;
                    }
                    // If we cannot move left anymore, we're finished
                    if (currentIndex == -1) {
                        break outerProcess;
                    }
                }
            }
        

        输出;

        0000
        0001
        0010
        0011
        0100
        0101
        0110
        0111
        1000
        1001
        1010
        1011
        1100
        1101
        1110
        1111
        

        【讨论】:

          【解决方案9】:
          • 无递归
          • 已订购
          • 可以通过索引获得特定组合(无需构建所有其他排列)
          • 支持并行迭代

          到底是类和main()方法:

          public class TwoDimensionalCounter<T> {
              private final List<List<T>> elements;
              private final int size;
          
              public TwoDimensionalCounter(List<List<T>> elements) {
                  //Need to reverse the collection if you need the original order in the answers
                  this.elements = Collections.unmodifiableList(elements);
                  int size = 1;
                  for(List<T> next: elements)
                      size *= next.size();
                  this.size = size;
              }
          
              public List<T> get(int index) {
                  List<T> result = new ArrayList<>();
                  for(int i = elements.size() - 1; i >= 0; i--) {
                      List<T> counter = elements.get(i);
                      int counterSize = counter.size();
                      result.add(counter.get(index % counterSize));
                      index /= counterSize;
                  }
                  return result;
              }
          
              public int size() {
                  return size;
              }
          
              public static void main(String[] args) {
                  TwoDimensionalCounter<Integer> counter = new TwoDimensionalCounter<>(
                          Arrays.asList(
                                  Arrays.asList(1, 2, 3),
                                  Arrays.asList(1, 2),
                                  Arrays.asList(1, 2, 3)
                          ));
                  for(int i = 0; i < counter.size(); i++)
                      System.out.println(counter.get(i));
              }
          }
          

          PS:事实证明Guava's Cartessian Product 使用相同的算法。但他们也为 List 创建了特殊的子类,使其效率提高数倍。

          【讨论】:

          • 请问你为什么使用 index /= counterSize; ?因为它不是必需品。
          • 您有三个插槽,可能具有值 a、b、c,因此排列将从:aaaaab 等开始。您描述的操作让我们首先生成算法3d 字母,然后移动到第 2 个字母,然后移动到第 1 个字母。
          【解决方案10】:

          使用 Java 8 Stream mapreduce 方法生成组合。

          Try it online!

          public static <T> List<List<T>> combinations(List<List<T>> lists) {
              // incorrect incoming data
              if (lists == null) return Collections.emptyList();
              return lists.stream()
                      // non-null and non-empty lists
                      .filter(list -> list != null && list.size() > 0)
                      // represent each list element as a singleton list
                      .map(list -> list.stream().map(Collections::singletonList)
                              // Stream<List<List<T>>>
                              .collect(Collectors.toList()))
                      // summation of pairs of inner lists
                      .reduce((list1, list2) -> list1.stream()
                              // combinations of inner lists
                              .flatMap(inner1 -> list2.stream()
                                      // merge two inner lists into one
                                      .map(inner2 -> Stream.of(inner1, inner2)
                                              .flatMap(List::stream)
                                              .collect(Collectors.toList())))
                              // list of combinations
                              .collect(Collectors.toList()))
                      // otherwise an empty list
                      .orElse(Collections.emptyList());
          }
          
          public static void main(String[] args) {
              List<String> list1 = Arrays.asList("A", "B", "C");
              List<String> list2 = Arrays.asList("W", "X", "Y", "Z");
              List<String> list3 = Arrays.asList("L", "M", "K");
          
              List<List<String>> lists = Arrays.asList(list1, list2, list3);
              List<List<String>> combinations = combinations(lists);
          
              // column-wise output
              int rows = 6;
              IntStream.range(0, rows).forEach(i -> System.out.println(
                      IntStream.range(0, combinations.size())
                              .filter(j -> j % rows == i)
                              .mapToObj(j -> combinations.get(j).toString())
                              .collect(Collectors.joining(" "))));
          }
          

          按列输出:

          [A, W, L] [A, Y, L] [B, W, L] [B, Y, L] [C, W, L] [C, Y, L]
          [A, W, M] [A, Y, M] [B, W, M] [B, Y, M] [C, W, M] [C, Y, M]
          [A, W, K] [A, Y, K] [B, W, K] [B, Y, K] [C, W, K] [C, Y, K]
          [A, X, L] [A, Z, L] [B, X, L] [B, Z, L] [C, X, L] [C, Z, L]
          [A, X, M] [A, Z, M] [B, X, M] [B, Z, M] [C, X, M] [C, Z, M]
          [A, X, K] [A, Z, K] [B, X, K] [B, Z, K] [C, X, K] [C, Z, K]
          

          另见:Cartesian product of an arbitrary number of sets

          【讨论】:

            【解决方案11】:

            这是一个使用位掩码的示例。没有递归和多个列表

            static List<Integer> allComboMatch(List<Integer> numbers, int target) {
                int sz = (int)Math.pow(2, numbers.size());
                for (int i = 1; i < sz; i++) {
                    int sum = 0;
                    ArrayList<Integer> result = new ArrayList<Integer>();
                    for (int j = 0; j < numbers.size(); j++) {
                        int x = (i >> j) & 1;
                        if (x == 1) {
                            sum += numbers.get(j);
                            result.add(j);
                        }
                    }
                    if (sum == target) {
                        return result;
                    }
                }
                return null;
            }
            

            【讨论】:

            • 这段代码生成numbers的所有子集的总和,与实际问题无关。它还返回加起来为特定总和的元素的索引,这同样不相关。
            猜你喜欢
            • 2015-12-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多