22

我有一个问题确实是一个一般的编程问题,但我的实现是用 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 中有多少元素,也不知道每个后续向量中有多少元素。我找到了与您希望单个数组中所有元素的每个唯一组合的情况相匹配的答案,但没有什么完全适合这个情况。

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

4

12 回答 12

22

尝试这样的事情:

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书籍之一实现了上述算法(在评论中@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]
于 2012-03-06T20:56:44.760 回答
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]
于 2013-04-04T23:59:17.577 回答
4

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

myListOfLists.combinations()

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

于 2014-09-15T17:21:23.343 回答
4

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

于 2015-05-12T21:42:33.000 回答
3

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

下面的代码是迭代的,基于 Pythonitertools.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]
于 2012-03-07T01:10:03.713 回答
2

您也可以使用函数式 Java 的 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);
 }
}
于 2012-07-24T23:03:35.263 回答
1

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

于 2012-03-06T21:02:28.927 回答
1

字符串向量的 LinkedHashMap 是...... - 麻烦。我不得不花费大量时间来转换解决方案以使用它,但最后,我没有生成 ArrayOfArrays,而是生成 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,您可以从每个子列表中选择模数,然后将剩余的数字除以前一个列表的大小和剩余的列表递归地传递给过程,直到没有剩下的列表。

于 2012-03-07T03:50:32.500 回答
1

我迟到了,但我关注了 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));                
            }
        }
    }       
于 2012-12-08T16:08:51.223 回答
1

递归解决方案:

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;
}
于 2019-11-28T08:32:06.907 回答
1

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

Lists.cartesianProduct(..)

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

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

于 2020-10-27T09:57:42.490 回答
0

您可以递归地生成组合。

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 
于 2016-10-19T09:28:31.950 回答