Java: декартово произведение списка списков

У меня проблема, которая на самом деле является вопросом общего программирования, но моя реализация выполняется на 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"]};

тогда моя строка [] [] должна выглядеть так:

{
   {"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, и сколько элементов в каждом последующем Vector. Я нашел ответы, которые соответствуют случаю, когда вам нужна каждая уникальная комбинация всех элементов в одном массиве, но ничего не подходит для этого. Если это точный дубликат вопроса, пожалуйста, поместите ссылку в ответ, и я закрою вопрос.

update. Я изменил свои типы на строки, потому что мой пример реального мира - это строки. Я пытался использовать целые числа, чтобы сделать этот пример более удобочитаемым, но ответы, которые я получил до сих пор, не переводят строки на строки. Итак, да, это цифры, но в моем фактическом случае они будут нитями, которые не имели бы никакого смысла никому, кроме людей, которые используют это конкретное приложение. так что это просто абстракция.

Ответы

Ответ 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 (извините, не лучше ссылки...).

Обратите внимание, что я назвал массивы set, но, конечно, им не нужны уникальные элементы. Когда я использовал его, они содержали уникальные элементы, отсюда и название.

ИЗМЕНИТЬ

Это в значительной степени перевод на 1-на-1:

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]

Ответ 2

Как насчет генерации продукта лениво, т.е. только создайте кортеж при его доступе?

/**
* 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]

Ответ 3

Я знаю это задолго до того, как вам был нужен ответ, но почему-то я не могу воздержаться от замечания, что можно переключиться на Groovy, по крайней мере для какой-то части приложения Java, и написать класс-оболочку, чтобы он соответствовал желаемому интерфейс. Код Groovy для таких перестановок

myListOfLists.combinations()

С тех пор, как я начал использовать Groovy в своих Java-приложениях, гораздо быстрее написать их и сделать более интересными для их отладки/профиля (ehem...)

Ответ 4

Взгляните на следующие два метода: они делают именно то, что вы просили. Я написал их как общий, неважно, как долго ваши списки или сколько ключей существует на карте, генерируемые комбинации верны.

Нижеприведенный код является итеративным, основанным на алгоритме функции 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]

Ответ 5

Вы также можете легко решить это с помощью Функциональной монады списка Java:

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

Ответ 6

В Guava есть утилитный метод, который возвращает декартово произведение данного списка множеств: Sets.cartesianProduct.

Ответ 7

Вот ссылка , ее С#, но я уверен, что вы могли бы с этим справиться!

Ответ 8

СвязаннаяHashMap векторов строк -... - хлопотно. Мне пришлось потратить много времени на преобразование решения для его использования, но в конце концов я не создаю 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,...), и у вас есть 3 возможности в pos 0, 2 в pos 1, 4 в pos 3 и т.д., поэтому 3 * 2 * 4 комбинации до сих пор.

С номерами от 0 до 23 вы можете выбрать из каждого подсписок с модулем и передать остальную часть номера, деленную на размер предыдущего списка, и оставшиеся списки рекурсивно на процедуру, пока список не останется.

Ответ 9

Я опаздываю на вечеринку, но я следовал за ссылкой Shiomi и перевел функции на Java. Результат - легко следовать и понимать алгоритм (я могу быть немного медленным, так как мне было трудно понять решение Барта Кирса).

Здесь (ключ - это 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 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