【问题标题】:Cartesian Product of a List of Lists列表列表的笛卡尔积
【发布时间】:2012-03-24 09:29:29
【问题描述】:

我有一个问题确实是一个一般性的编程问题,但我的实现是用 Java 实现的,所以我会以这种方式提供我的示例

我有这样的课:

public class Foo {
    LinkedHashMap<String, Vector<String>> dataStructure;

    public Foo(LinkedHashMap<String, Vector<String>> dataStructure) {
        this.dataStructure = dataStructure;
    }

    public String[][] allUniqueCombinations() {
        //this is what I need to do
    }
}

我需要从我的LinkedHashMap 生成一个嵌套数组,它代表 LHM 中所有值的每个唯一组合。例如,如果我的 LHM 看起来像这样(伪代码,但我认为您可以理解..):

{"foo" => ["1","2","3"], "bar" => ["3","2"], "baz" => ["5","6","7"]};

那么我的String[][] 应该是这样的:

{
   {"foo","bar","baz"},
   {"1","3","5"},
   {"1","2","5"},
   {"1","3","6"},
   {"1","2","6"},
   {"1","3","7"},
   {"1","2","7"},
   {"2","3","5"},
   {"2","2","5"},
   {"2","3","6"},
   {"2","2","6"},
   {"2","3","7"},
   {"2","2","7"},
   {"3","3","5"},
   {"3","2","5"},
   {"3","3","6"},
   {"3","2","6"},
   {"3","3","7"},
   {"3","2","7"},
}

我想这就是全部,我是手动制作的(显然)所以我可能错过了一组,但我认为这说明了我正在尝试做的事情。只要存在所有独特的组合,每组的顺序无关紧要。另外要清楚的是,您不知道 LHM 中有多少元素,也不知道每个后续向量中有多少元素。我找到了与您希望单个数组中所有元素的每个唯一组合的情况相匹配的答案,但没有什么完全适合这个。

更新:我将类型更改为字符串,因为我的真实示例实际上是字符串。我试图使用整数来使示例更具可读性,但是到目前为止我得到的答案并不能很好地转换为字符串。所以,是的,它们是数字,但在我的实际情况下,它们将是对除了使用此特定应用程序的人之外的任何人都没有多大意义的字符串。所以,这只是它的抽象。

【问题讨论】:

  • 快速提问 - 为什么是Vectorstackoverflow.com/questions/1386275/…
  • 为什么所有的uniqueCombinations都是长度为3的?输入中的条目[3,2]有什么意义?
  • @josh.trow 它是一个向量,因为它是一个向量。我没有给你很好的解释。我不是静态类型的忠实粉丝。
  • @Adam 唯一组合的长度为 3,因为在这种情况下 dataStructure.size() == 3。如果 dataStructure 的顶层有 4 个元素,那么每个元素都是 4..
  • LHM中Integer键的意义是什么?组合数组的排序应该基于键的顺序,还是 LHM 的排序? (即,如果我按顺序将键 3、2、1 添加到 LHM,我应该使用相同的顺序还是 1、2、3)假设我有键 1、2、4、6 的映射......应该组合数组跳过 3 和 5 还是使用一些特殊值? (例如 0、-1 等)

标签: java data-structures recursion nested-loops linkedhashmap


【解决方案1】:

试试这样的:

public static void generate(int[][] sets) {
    int solutions = 1;
    for(int i = 0; i < sets.length; solutions *= sets[i].length, i++);
    for(int i = 0; i < solutions; i++) {
        int j = 1;
        for(int[] set : sets) {
            System.out.print(set[(i/j)%set.length] + " ");
            j *= set.length;
        }
        System.out.println();
    }
}

public static void main(String[] args) {
    generate(new int[][]{{1,2,3}, {3,2}, {5,6,7}});
}

将打印:

1 3 5
2 3 5
3 3 5
1 2 5
2 2 5
3 2 5
1 3 6
2 3 6
3 3 6
1 2 6
2 2 6
3 2 6
1 3 7
2 3 7
3 3 7
1 2 7
2 2 7
3 2 7

我已经根据(我相信)Knuth 的一本TAOCP 书籍实现了上述算法(在 cmets @chikitin 中有更具体的参考:它在 ??? ??PRE FASCICLE 2A 部分 7.2.1.1生成所有 n 元组,Knuth、Addison Wesley 的计算机编程艺术)。

请注意,我已将数组命名为 set,但它们当然不需要包含唯一元素。我使用它的时候,它们确实包含独特的元素,因此得名。

编辑

这几乎是一对一的翻译:

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Vector;

public class Foo {

    private LinkedHashMap<String, Vector<String>> dataStructure;

    public Foo(LinkedHashMap<String, Vector<String>> dataStructure){
        this.dataStructure = dataStructure;
    }

    public String[][] allUniqueCombinations(){
        int n = dataStructure.keySet().size();
        int solutions = 1;

        for(Vector<String> vector : dataStructure.values()) {
            solutions *= vector.size();            
        }

        String[][] allCombinations = new String[solutions + 1][];
        allCombinations[0] = dataStructure.keySet().toArray(new String[n]);

        for(int i = 0; i < solutions; i++) {
            Vector<String> combination = new Vector<String>(n);
            int j = 1;
            for(Vector<String> vec : dataStructure.values()) {
                combination.add(vec.get((i/j)%vec.size()));
                j *= vec.size();
            }
            allCombinations[i + 1] = combination.toArray(new String[n]);
        }

        return allCombinations;
    }

    public static void main(String[] args) {
        LinkedHashMap<String, Vector<String>> data = new LinkedHashMap<String, Vector<String>>();
        data.put("foo", new Vector<String>(Arrays.asList("1", "2", "3")));
        data.put("bar", new Vector<String>(Arrays.asList("3", "2")));
        data.put("baz", new Vector<String>(Arrays.asList("5", "6", "7")));

        Foo foo = new Foo(data);

        for(String[] combination : foo.allUniqueCombinations()) {
            System.out.println(Arrays.toString(combination));            
        }
    }
}

如果你运行上面的类,会打印以下内容:

[foo, bar, baz]
[1, 3, 5]
[2, 3, 5]
[3, 3, 5]
[1, 2, 5]
[2, 2, 5]
[3, 2, 5]
[1, 3, 6]
[2, 3, 6]
[3, 3, 6]
[1, 2, 6]
[2, 2, 6]
[3, 2, 6]
[1, 3, 7]
[2, 3, 7]
[3, 3, 7]
[1, 2, 7]
[2, 2, 7]
[3, 2, 7]

【讨论】:

  • 这并不能很好地转化为我的实际用例,我更新了这个问题,如果你想再试一次,我会很感激的。谢谢!
  • 好吧,因为 LHM 的值部分是 Vector,我不能说解决方案 *= sets[i].length。另外,我不能调用 dataStructure.get(i.toString()) 或类似的东西,因为键将是“foo”、“bar”、“baz”等。
  • 你可以遍历你的地图,你可以调用'size'而不是'length'。我没有发现任何问题。
  • 如何解释内部 for 循环的 set[(i/j)%set.length 部分和随后的 j *= vec.size();?或者这是一个技巧?
  • ???感谢您的解决方案。它在 ??? 2A 部分 7.2.1.1 生成所有 n 元组,由 Knuth, Addison Wesley 编写的计算机编程艺术。 }}}}}}}}}}}}}
【解决方案2】:

我知道在您需要答案之后很久,但不知何故,我不能不注意到人们可以切换到 Groovy,至少对于 Java 应用程序的某些部分,并编写一个包装类来匹配所需的接口。这种排列的 Groovy 代码是

myListOfLists.combinations()

自从我开始在我的 Java 应用程序中使用 Groovy 以来,编写它们的速度要快得多,调试/分析它们也更有趣(嗯...)

【讨论】:

  • 很棒的建议!
【解决方案3】:

Guava 有一个实用方法,它返回给定集合列表的笛卡尔积:Sets.cartesianProduct

【讨论】:

    【解决方案4】:

    如何懒惰地生成产品,即。仅在访问元组时创建元组?

    /**
    * A random access view of tuples of a cartesian product of ArrayLists
    *
    * Orders tuples in the natural order of the cartesian product
    *
    * @param T the type for both the values and the stored tuples, ie. values of the cartesian factors are singletons
    * While the type of input sets is List<T> with elements being treated as singletons
    *
    */
    
    abstract public class CartesianProductView<T> extends AbstractList<T> {
    
    private final List<List<T>> factors;
    private final int size;
    
    /**
     * @param factors the length of the factors (ie. the elements of the factors argument) should not change,
     *  otherwise get may not return all tuples, or throw exceptions when trying to access the factors outside of range
     */
    public CartesianProductView(List<List<T>> factors) {
        this.factors = new ArrayList<>(factors);
        Collections.reverse(this.factors);
        int acc = 1;
        for (Iterator<List<T>> iter = this.factors.iterator(); iter.hasNext(); ) {
            acc *= iter.next().size();
        }
        this.size = acc;
    }
    
    @Override
    public T get(int index) {
        if (index < 0 || index >= size()) {
            throw new IndexOutOfBoundsException(String.format("index %d > size() %d", index, size()));
        }
    
        T acc = null;
        for (Iterator<List<T>> iter = factors.iterator(); iter.hasNext();) {
            List<T> set = iter.next();
            acc = makeTupleOrSingleton(set.get(index % set.size()), acc);
            index /= set.size();
        }
        return acc;
    }
    
    @Override
    public int size() {
        return size;
    }
    
    private T makeTupleOrSingleton(T left, T right) {
        if (right == null) {
            return left;
        }
        return makeTuple(left, right);
    }
    
    /**
     *
     * @param left      a singleton of a value
     * @param right     a tuple of values taken from the cartesian product factors, with null representing the empty set
     * @return          the sum of left and right, with the value of left being put in front
     */
    abstract protected T makeTuple(T left, T right);
    }
    

    并像这样使用它

    final List<List<String>> l1 = new ArrayList<List<String>>() {{ add(singletonList("a")); add(singletonList("b")); add(singletonList("c")); }};
    final List<List<String>> l2 = new ArrayList<List<String>>() {{ add(singletonList("X")); add(singletonList("Y")); }};
    final List<List<String>> l3 = new ArrayList<List<String>>() {{ add(singletonList("1")); add(singletonList("2")); add(singletonList("3")); add(singletonList("4")); }};
    
    
    List<List<List<String>>> in = new ArrayList<List<List<String>>>() {{ add(l1); add(l2); add(l3); }};
    
    List<List<String>> a = new CartesianProductView<List<String>>(in) {
    
        @Override
        protected List<String> makeTuple(final List<String> left, final List<String> right) {
            return new ArrayList<String>() {{ add(left.get(0)); addAll(right); }};
        }
    
    };
    
    System.out.println(a);
    

    结果:

    [[a, X, 1], [a, X, 2], [a, X, 3], [a, X, 4], [a, Y, 1], [a, Y, 2], [a, Y, 3], [a, Y, 4], [b, X, 1], [b, X, 2], [b, X, 3], [b, X, 4], [b, Y, 1], [b, Y, 2], [b, Y, 3], [b, Y, 4], [c, X, 1], [c, X, 2], [c, X, 3], [c, X, 4], [c, Y, 1], [c, Y, 2], [c, Y, 3], [c, Y, 4]]
    

    作为一个额外的好处,你可以使用它来连接所有字符串:

    final List<String> l1 = new ArrayList<String>() {{ add("a"); add("b"); add("c"); }};
    final List<String> l2 = new ArrayList<String>() {{ add("X"); add("Y"); }};
    final List<String> l3 = new ArrayList<String>() {{ add("1"); add("2"); add("3"); add("4"); }};
    
    
    List<List<String>> in = new ArrayList<List<String>>() {{ add(l1); add(l2); add(l3); }};
    
    List<String> a = new CartesianProductView<String>(in) {
    
        @Override
        protected String makeTuple(String left, String right) {
            return String.format("%s%s", left, right);
        }
    
    };
    
    System.out.println(a);
    

    结果:

    [aX1, aX2, aX3, aX4, aY1, aY2, aY3, aY4, bX1, bX2, bX3, bX4, bY1, bY2, bY3, bY4, cX1, cX2, cX3, cX4, cY1, cY2, cY3, cY4]
    

    【讨论】:

      【解决方案5】:

      看看以下两种方法,它们完全符合您的要求。我写它们是通用的,无论您的列表有多长或地图中存在多少键,生成的组合都是正确的。

      下面的代码是迭代,基于 Python 的 itertools.product() 函数的算法,用于计算列表列表的笛卡尔积。

      public String[][] allUniqueCombinations() {
      
          List<String> labels = new ArrayList<String>();
          List<List<String>> lists = new ArrayList<List<String>>();
      
          for (Map.Entry<String, Vector<String>> entry : dataStructure.entrySet()) {
              labels.add(entry.getKey());
              lists.add(entry.getValue());
          }
      
          List<List<String>> combinations = product(lists);
          int m = combinations.size() + 1;
          int n = labels.size();
          String[][] answer = new String[m][n];
      
          for (int i = 0; i < n; i++)
              answer[0][i] = labels.get(i);
          for (int i = 1; i < m; i++)
              for (int j = 0; j < n; j++)
                  answer[i][j] = combinations.get(i-1).get(j);
      
          return answer;
      
      }
      
      private List<List<String>> product(List<List<String>> lists) {
      
          List<List<String>> result = new ArrayList<List<String>>();
          result.add(new ArrayList<String>());
      
          for (List<String> e : lists) {
              List<List<String>> tmp1 = new ArrayList<List<String>>();
              for (List<String> x : result) {
                  for (String y : e) {
                      List<String> tmp2 = new ArrayList<String>(x);
                      tmp2.add(y);
                      tmp1.add(tmp2);
                  }
              }
              result = tmp1;
          }
      
          return result;
      
      }
      

      我用问题中的例子对它们进行了测试:

      LinkedHashMap<String, Vector<String>> sample = 
          new LinkedHashMap<String, Vector<String>>();
      
      Vector<String> v1 = new Vector<String>();
      v1.add("1"); v1.add("2"); v1.add("3");
      Vector<String> v2 = new Vector<String>();
      v2.add("3"); v2.add("2");
      Vector<String> v3 = new Vector<String>();
      v3.add("5"); v3.add("6"); v3.add("7");
      
      sample.put("foo", v1);
      sample.put("bar", v2);
      sample.put("baz", v3);
      
      Foo foo = new Foo(sample);
      String[][] ans = foo.allUniqueCombinations();
      for (String[] row : ans)
          System.out.println(Arrays.toString(row));
      

      打印出来的答案是预期的(尽管组合以不同的顺序出现):

      [foo, bar, baz]
      [1, 3, 5]
      [1, 3, 6]
      [1, 3, 7]
      [1, 2, 5]
      [1, 2, 6]
      [1, 2, 7]
      [2, 3, 5]
      [2, 3, 6]
      [2, 3, 7]
      [2, 2, 5]
      [2, 2, 6]
      [2, 2, 7]
      [3, 3, 5]
      [3, 3, 6]
      [3, 3, 7]
      [3, 2, 5]
      [3, 2, 6]
      [3, 2, 7]
      

      【讨论】:

      • Oscar,很难决定是接受你还是Bart的回答,我只好接受Bart的,因为他先回答,但你的也很好,谢谢!
      【解决方案6】:

      您也可以使用Functional Java's List monad 轻松解决这个问题:

      import fj.data.List;
      
      public class cartesian {
       public static void main(String[] args) {
        List<String>  foo = List.list("a", "b");
        List<Integer> bar = List.list(1,2,3);
        List<Float>   baz = List.list(0.2f,0.4f,0.3f);
      
        List<P3<String, Integer, Float>> 
        // the Cartesian product is assembled into a list of P3's
        result = foo.bind(bar, baz, P.<String, Integer, Float>p3()); 
      
        String out = Show.listShow(Show.p3Show(Show.stringShow, Show.intShow, Show.floatShow))
                     .showS(result);
        System.out.println(out);
       }
      }
      

      【讨论】:

        【解决方案7】:

        这是一个link,它的 c#,但我相信你可以使用它!

        【讨论】:

          【解决方案8】:

          字符串向量的 LinkedHashMap 是... - 很麻烦。我不得不花费大量时间来转换解决方案以使用它,但最后,我没有生成 ArrayOfArrays,而是生成 List 的 List,并将最后一步留给读者。

          import java.util.*;
          /**
              CartesianProductLHM   
          */
          public class CartesianProductLHM
          {
              LinkedHashMap <String, Vector<String>> dataStructure;
          
              public CartesianProductLHM (final String[] data) {
                  dataStructure = new LinkedHashMap <String, Vector<String>> ();
                  for (String str : data)
                  {
                      String [] kv = str.split (":");
                      String [] values = kv[1].split (","); 
                      Vector <String> v = new Vector <String> ();
                      for (String s: values) {
                          v.add (s);
                      //  System.out.print (s); 
                      }
                      // System.out.println ("\n---");
                      dataStructure.put (kv[0], v);
                  }
                  // System.out.println ("    --- --- ---");
              }
          
              List <String> getCombiFor (final int i, final List <List <String>> livs) 
              {
                  List <String> ls = new ArrayList <String> ();
                  if (! livs.isEmpty ()) {
                      List <String> vs = livs.remove (0); 
                      int idx = i % vs.size (); 
                      String elem = vs.get (idx);
                      ls.add (elem);
                      ls.addAll (getCombiFor (i / vs.size (), livs));
                  }
                  return ls;
              }
          
              List <String> getOuterCombiFor (int i, List <List <String>> coll) 
              {
                  List <String> ls = new ArrayList <String> ();
                  if (! coll.isEmpty ()) {
                      List <List <String>> livs = new ArrayList <List <String>> ();
                      for (List<String> li : coll) 
                      {
                          livs.add (li);
                      }   
                      ls.addAll (getCombiFor (i, livs));
                  } 
                  return ls;  
              }   
          
              public List <List <String>> allUniqueCombinations () {
                  Collection <Vector <String>> li = dataStructure.values (); 
                  List <List <String>> lls = new ArrayList <List <String>> ();
                  for (Vector <String> vs : li) {
                      List <String> l = new ArrayList <String> ();
                      for (String s : vs) {
                          l.add (s);
                      }
                      lls.add (l);
                  }
                  int count = 1;
                  for (Vector <String> vec: li) {
                      count *= vec.size ();
                  }       
                  List <List <String>> result = new ArrayList <List <String>> ();
                  for (int i = 0; i < count; ++i) 
                  {
                      List <String> l = getOuterCombiFor (i, lls);
                      result.add (l);
                  }
                  return result;  
              }
          
              public static void main (String args[])
              {
                  String[] arr = {"foo:1,2,3", "bar:a,b", "baz:5,6,7"};
                  CartesianProductLHM cp = new CartesianProductLHM (arr);
                  List <List <String>> lls = cp.allUniqueCombinations ();
                  for (List <String> ls : lls) 
                  {
                      for (String s : ls)
                          System.out.print (s + "\t");
                      System.out.println ();
                  }
              }
          }
          

          嗯 - 是的,我解析了一些测试数据。

          主要思想是,你有一些列表(abc,12,defg,...),你在 pos 0 有 3 种可能性,在 pos 1 有 2 种可能性,在 pos 3 有 4 种可能性,依此类推,所以 3*2 *到目前为止有 4 种组合。

          从数字 0 到 23 中,您可以从每个子列表中选择模数,然后将剩余的数字除以前一个列表的大小和剩余的列表递归地传递给过程,直到没有剩余列表。

          【讨论】:

            【解决方案9】:

            我迟到了,但我按照 Shiomi 的链接将函数翻译成 Java。结果是一个易于理解和理解的算法(我可能有点慢,因为我很难理解 Bart Kiers 的解决方案)。

            在这里(关键是一个int,替换成String应该很简单):

            用法

                public void testProduct(){
                    Map<Integer, List<String>> data =   new LinkedHashMap<Integer, List<String>>(){{                
                        put(0, new ArrayList<String>(){{
                            add("John"); add("Sarah");                      
                        }});                
                        put(1, new ArrayList<String>(){{
                            add("Red"); add("Green"); add("Blue"); add("Orange");
                        }});
                        put(2, new ArrayList<String>(){{
                            add("Apple"); add("Tomatoe"); add("Bananna");                   
                        }});
                }};
            
                    List<String[]> product =  GetCrossProduct(data);
                    for(String[] o : product)
                        System.out.println(Arrays.toString(o));
            
                }
            

            结果

            [John, Red, Apple]
            [John, Red, Tomatoe]
            [John, Red, Bananna]
            [John, Green, Apple]
            [John, Green, Tomatoe]
            [John, Green, Bananna]
            [John, Blue, Apple]
            [John, Blue, Tomatoe]
            [John, Blue, Bananna]
            [John, Orange, Apple]
            [John, Orange, Tomatoe]
            [John, Orange, Bananna]
            [Sarah, Red, Apple]
            [Sarah, Red, Tomatoe]
            [Sarah, Red, Bananna]
            [Sarah, Green, Apple]
            [Sarah, Green, Tomatoe]
            [Sarah, Green, Bananna]
            [Sarah, Blue, Apple]
            [Sarah, Blue, Tomatoe]
            [Sarah, Blue, Bananna]
            [Sarah, Orange, Apple]
            [Sarah, Orange, Tomatoe]
            [Sarah, Orange, Bananna]
            

            笛卡尔积函数

                public static List<String[]> GetCrossProduct(Map<Integer, List<String>> lists)
                {
                    List<String[]> results = new ArrayList<String[]>();
                    GetCrossProduct(results, lists, 0, new String[(lists.size())]);
                    return results;
                }
            
                private void GetCrossProduct(List<String[]> results, Map<Integer, List<String>> lists, int depth, String[] current)
                {
                    for (int i = 0; i < lists.get(depth).size(); i++)
                    {
                        current[depth] = lists.get(depth).get(i);            
                        if (depth < lists.keySet().size() - 1)
                            GetCrossProduct(results, lists, depth + 1, current);
                        else{
                            results.add(Arrays.copyOf(current,current.length));                
                        }
                    }
                }       
            

            【讨论】:

              【解决方案10】:

              递归解法:

              public <T> List<List<T>> cartesianProduct(int i, List<T>... a) {
                  if(i == a.length ) {
                      List<List<T>> result = new ArrayList<>();
                      result.add(new ArrayList());
                      return result;
                  }
                  List<List<T>> next = cartesianProduct(i+1, a);
                  List<List<T>> result = new ArrayList<>();
                  for(int j=0; j < a[i].size(); j++) {
                      for(int k=0; k < next.size(); k++) {
                          List<T> concat = new ArrayList();
                          concat.add(a[i].get(j));
                          concat.addAll(next.get(k));
                          result.add(concat);
                      }
                  }
                  return result;
              }
              

              【讨论】:

                【解决方案11】:

                感谢 Vitalii Fedorenko,我可以通过使用实现相同的列表

                Lists.cartesianProduct(..)

                来自https://guava.dev/releases/23.5-jre/api/docs/com/google/common/collect/Lists.html#cartesianProduct-java.util.List...-

                我认为,如果生产代码需要它,那么最好依赖像 Guava 这样久经考验的库,而不是构建我们自己的库。

                【讨论】:

                  【解决方案12】:

                  您可以递归地生成组合。

                  public class Main {
                      public static void main(String[] args) {
                          int[][] arr = new int[][] { { 1, 2, 3 }, { 3, 2 }, { 5, 6, 7 } };
                          cartesianProduct(arr, 0, new int[arr.length]);
                      }
                  
                      private static void cartesianProduct(int[][] arr, int level, int[] cp) {
                          if (level == arr.length) {
                              for (int x : cp)
                                  System.out.print(x + " ");
                              System.out.println();
                              return;
                          }
                  
                          for (int i = 0; i < arr[level].length; i++) {
                              cp[level] = arr[level][i];
                              cartesianProduct(arr, level + 1, cp);
                          }
                      }
                  }
                  

                  输出:

                  1 3 5 
                  1 3 6 
                  1 3 7 
                  1 2 5 
                  1 2 6 
                  1 2 7 
                  2 3 5 
                  2 3 6 
                  2 3 7 
                  2 2 5 
                  2 2 6 
                  2 2 7 
                  3 3 5 
                  3 3 6 
                  3 3 7 
                  3 2 5 
                  3 2 6 
                  3 2 7 
                  

                  【讨论】:

                    猜你喜欢
                    • 2015-12-05
                    • 1970-01-01
                    • 2017-07-15
                    • 2012-01-03
                    • 2021-10-22
                    • 2011-07-10
                    • 2020-12-21
                    • 1970-01-01
                    • 2023-04-10
                    相关资源
                    最近更新 更多