web-dev-qa-db-fra.com

Générez toutes les combinaisons à partir de plusieurs listes

Étant donné un nombre inconnu de listes, chacune avec une longueur inconnue, je dois générer une liste singulière avec toutes les combinaisons uniques possibles. Par exemple, étant donné les listes suivantes:

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

Ensuite, je devrais pouvoir générer 12 combinaisons:

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

Si une troisième liste de 3 éléments était ajoutée, j'aurais 36 combinaisons, et ainsi de suite.

Des idées sur la façon dont je peux faire cela en Java?
(le pseudo-code conviendrait également)

41
Michael Hillman

Vous avez besoin de récursivité:

Disons que toutes vos listes sont dans lists, qui est une liste de listes. Soit result la liste de vos permutations requises. Vous pouvez l'implémenter comme ceci:

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

L'appel ultime sera comme ceci:

generatePermutations(lists, result, 0, "");
56
Armen Tsirunyan

Ce sujet a été utile. J'ai entièrement réécrit la solution précédente en Java et plus convivial. De plus, j'utilise des collections et des génériques pour plus de flexibilité:

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

J'utilise la bibliothèque de goyaves pour la création de collections.

16
Víctor

Cette opération est appelée produit cartésien . La goyave fournit une fonction utilitaire pour cela: Lists.cartesianProduct

7
Pedro Oliveira

Sans récursion nique combinaisons:

    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+" ");    
5
Ruslan Ostafiichuk

Ajout d'une réponse basée sur un itérateur pour travailler sur une liste générique de listes List<List<T>>, étendant l'idée de la réponse de Ruslan Ostafiichuk. L'idée que j'ai suivie était:

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

Maintenant, le code. J'ai utilisé un Set simplement pour éliminer les doublons. Peut être remplacé par un List. Tout devrait fonctionner de manière transparente. :)

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

Un petit bloc de test ..

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
Debosmit Ray

Utilisez la solution de boucle imbriquée fournie par d'autres réponses ici pour combiner deux listes.

Lorsque vous avez plus de deux listes,

  1. Combinez les deux premiers dans une nouvelle liste.
  2. Combinez la liste résultante avec la liste d'entrée suivante.
  3. Répéter.
3
mbeckish

en retard à la fête comme d'habitude, mais voici un exemple bien expliqué utilisant des tableaux, il peut facilement être modifié pour les listes. J'avais besoin de toutes les combinaisons uniques de plusieurs tableaux pour mon cas d'utilisation dans l'ordre lexicographique.

Je l'ai posté car aucune des réponses ici ne donne un algorithme clair, et je ne supporte pas la récursivité. Sommes-nous pas sur stackoverflow après tout?

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

Le résultat;

0000

0001

0010

0011

0100

0101

0110

0111

1000

1001

1010

1011

1100

1101

1110

1111

2
professorcolm
  • Pas de récursivité
  • Est commandé
  • Possible d'obtenir une combinaison spécifique par son index (sans construire toutes les autres permutations):

Classe et méthode main() à la fin:

public class TwoDimensionalCounter<T> {
    private final List<List<T>> elements;

    public TwoDimensionalCounter(List<List<T>> elements) {
        this.elements = Collections.unmodifiableList(elements);
    }

    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;//Collections.reverse() if you need the original order
    }

    public int size() {
        int result = 1;
        for(List<T> next: elements) result *= next.size();
        return result;
    }

    public static void main(String[] args) {
        TwoDimensionalCounter<Integer> counter = new TwoDimensionalCounter<>(
                Arrays.asList(
                        Arrays.asList(1, 2, 3),
                        Arrays.asList(1, 2, 3),
                        Arrays.asList(1, 2, 3)
                ));
        for(int i = 0; i < counter.size(); i++)
            System.out.println(counter.get(i));
    }
}
2

L'opération que vous devez implémenter s'appelle Produit cartésien . Pour plus de détails, voir https://en.wikipedia.org/wiki/Cartesian_product

Je recommande d'utiliser ma bibliothèque open source qui peut faire exactement ce dont vous avez besoin: https://github.com/SurpSG/Kombi

Il y a un exemple comment l'utiliser: https://github.com/SurpSG/Kombi#usage-for-lists-1

Remarque : La bibliothèque a été conçue à des fins hautes performances . Vous pouvez observer les résultats des banchmarks ici

La bibliothèque vous offre un assez bon débit et une utilisation constante de la mémoire

1
SergiiGnatiuk