web-dev-qa-db-fra.com

Générer toutes les combinaisons possibles

Étant donné 2 tableaux Array1 = {a,b,c...n} et Array2 = {10,20,15....x}, comment puis-je générer toutes les combinaisons possibles sous forme de chaînes a(i) b(j) c(k) n(p) où 

1 <= i <= 10,  1 <= j <= 20 , 1 <= k <= 15,  .... 1 <= p <= x

Tel que:

a1 b1 c1 .... n1  
a1 b1 c1..... n2  
......  
......  
a10 b20 c15 nx (last combination)

Donc, dans tout nombre total de combinaison = produit d'éléments de array2 = (10 X 20 X 15 X ..X x)

Semblable à un produit cartésien, dans lequel le deuxième tableau définit la limite supérieure de chaque élément du premier tableau.

Exemple avec des nombres fixes, 

    Array x =  [a,b,c]
    Array y =  [3,2,4] 

Nous aurons donc 3 * 2 * 4 = 24 combinaisons. Les résultats devraient être:

    a1 b1 c1  
    a1 b1 c2  
    a1 b1 c3  
    a1 b1 c4  

    a1 b2 c1  
    a1 b2 c2  
    a1 b2 c3  
    a1 b2 c4


    a2 b1 c1  
    a2 b1 c2  
    a2 b1 c3  
    a2 b1 c4  

    a2 b2 c1  
    a2 b2 c2  
    a2 b2 c3  
    a2 b2 c4


    a3 b1 c1  
    a3 b1 c2  
    a3 b1 c3  
    a3 b1 c4  

    a3 b2 c1  
    a3 b2 c2  
    a3 b2 c3  
    a3 b2 c4 (last)
61
Amitd
using System;
using System.Text;

public static string[] GenerateCombinations(string[] Array1, int[] Array2)
{
    if(Array1 == null) throw new ArgumentNullException("Array1");
    if(Array2 == null) throw new ArgumentNullException("Array2");
    if(Array1.Length != Array2.Length)
        throw new ArgumentException("Must be the same size as Array1.", "Array2");

    if(Array1.Length == 0)
        return new string[0];

    int outputSize = 1;
    var current = new int[Array1.Length];
    for(int i = 0; i < current.Length; ++i)
    {
        if(Array2[i] < 1)
            throw new ArgumentException("Contains invalid values.", "Array2");
        if(Array1[i] == null)
            throw new ArgumentException("Contains null values.", "Array1");
        outputSize *= Array2[i];
        current[i] = 1;
    }

    var result = new string[outputSize];
    for(int i = 0; i < outputSize; ++i)
    {
        var sb = new StringBuilder();
        for(int j = 0; j < current.Length; ++j)
        {
            sb.Append(Array1[j]);
            sb.Append(current[j].ToString());
            if(j != current.Length - 1)
                sb.Append(' ');
        }
        result[i] = sb.ToString();
        int incrementIndex = current.Length - 1;
        while(incrementIndex >= 0 && current[incrementIndex] == Array2[incrementIndex])
        {
                current[incrementIndex] = 1;
                --incrementIndex;
        }
        if(incrementIndex >= 0)
            ++current[incrementIndex];
    }
    return result;
}
20
max

Chose sûre. C'est un peu délicat de faire cela avec LINQ mais cela est certainement possible en utilisant uniquement les opérateurs de requête standard. 

UPDATE: C'est le sujet de mon blog du lundi 28 juin 2010 ; merci pour la grande question. En outre, un commentateur sur mon blog a noté qu'il existait une requête encore plus élégante que celle que j'avais donnée. Je vais mettre à jour le code ici pour l'utiliser.

La difficulté consiste à créer le produit cartésien de nombreuses séquences arbitrairement. "Zipping" dans les lettres est trivial par rapport à cela. Vous devriez étudier cela pour vous assurer que vous comprenez comment cela fonctionne. Chaque partie est assez simple, mais la façon dont elles sont combinées demande de l’habitude:

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
    IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>()};
    return sequences.Aggregate(
        emptyProduct,
        (accumulator, sequence) => 
            from accseq in accumulator 
            from item in sequence 
            select accseq.Concat(new[] {item})                          
        );
 }

Pour expliquer comment cela fonctionne, commencez par comprendre ce que fait l'opération "accumuler". L'opération d'accumulation la plus simple est "tout ajouter dans cette séquence ensemble". Pour ce faire, procédez comme suit: commencez par zéro. Pour chaque élément de la séquence, la valeur actuelle de l'accumulateur est égale à la somme de l'élément et de la valeur précédente de l'accumulateur. Nous faisons la même chose, sauf qu'au lieu d'accumuler la somme en fonction de la somme obtenue jusqu'à présent et de l'élément en cours, nous accumulons le produit cartésien au fur et à mesure.

Pour ce faire, nous allons tirer parti du fait que nous avons déjà un opérateur dans LINQ qui calcule le produit cartésien de deux choses: 

from x in xs
from y in ys
do something with each possible (x, y)

En prenant à plusieurs reprises le produit cartésien de l'accumulateur avec l'élément suivant dans la séquence d'entrée et en collant un peu les résultats, nous pouvons générer le produit cartésien au fur et à mesure.

Alors pensez à la valeur de l'accumulateur. À des fins d'illustration, je vais montrer la valeur de l'accumulateur en tant que résultats des opérateurs de séquence qu'il contient. Ce n'est pas ce que contient l'accumulateur en fait. L’accumulateur contient en réalité les opérateurs qui produisent ces résultats. L’opération entière ici ne fait que construire une massive arborescence d’opérateurs de séquence, dont le résultat est le produit cartésien. Mais le produit cartésien final n'est pas calculé jusqu'à ce que la requête soit exécutée. À titre d'illustration, je vais montrer ce que sont les résultats à chaque étape du processus. contient les opérateurs qui produisent ces résultats.

Supposons que nous prenions le produit cartésien de la séquence de séquences {{1, 2}, {3, 4}, {5, 6}}. L'accumulateur commence par une séquence contenant une séquence vide: { { } }

Lors de la première accumulation, l'accumulateur est {{}} et l'élément est {1, 2}. Nous faisons ceci:

from accseq in accumulator
from item in sequence 
select accseq.Concat(new[] {item})

Donc nous prenons le produit cartésien de { { } } avec {1, 2}, et pour chaque paire, nous concaténons: Nous avons la paire ({ }, 1), donc nous concaténons { } et {1} pour obtenir {1}. Nous avons la paire ({ }, 2}), donc nous concaténons { } et {2} pour obtenir {2}. Nous avons donc {{1}, {2}} comme résultat.

Ainsi, lors de la deuxième accumulation, l’accumulateur est {{1}, {2}} et l’item est {3, 4}. De nouveau, nous calculons le produit cartésien de ces deux séquences pour obtenir:

 {({1}, 3), ({1}, 4), ({2}, 3), ({2}, 4)}

et ensuite à partir de ces éléments, concaténez le second sur le premier. Le résultat est donc la séquence {{1, 3}, {1, 4}, {2, 3}, {2, 4}}, ce que nous voulons.

Maintenant, nous accumulons à nouveau. On prend le produit cartésien de l'accumulateur avec {5, 6} pour obtenir 

 {({ 1, 3}, 5), ({1, 3}, 6), ({1, 4}, 5), ...

puis concaténez le deuxième élément sur le premier pour obtenir:

{{1, 3, 5}, {1, 3, 6}, {1, 4, 5}, {1, 4, 6} ... }

et nous avons fini. Nous avons accumulé le produit cartésien.

Maintenant que nous avons une fonction d’utilité qui peut prendre le produit cartésien de nombreuses séquences de manière arbitraire, le reste est facile par comparaison:

var arr1 = new[] {"a", "b", "c"};
var arr2 = new[] { 3, 2, 4 };
var result = from cpLine in CartesianProduct(
                 from count in arr2 select Enumerable.Range(1, count)) 
             select cpLine.Zip(arr1, (x1, x2) => x2 + x1);

Et maintenant nous avons une séquence de séquences de chaînes, une séquence de chaînes par ligne:

foreach (var line in result)
{
    foreach (var s in line)
        Console.Write(s);
    Console.WriteLine();
}

Peasy facile!

144
Eric Lippert

Solution alternative:

Première étape: lisez ma série d'articles sur la façon de générer toutes les chaînes qui correspondent à une grammaire contextuelle:

http://blogs.msdn.com/b/ericlippert/archive/tags/grammars/

Deuxième étape: définissez une grammaire générant le langage souhaité. Par exemple, vous pouvez définir la grammaire:

S: a A b B c C
A: 1 | 2 | 3
B: 1 | 2
C: 1 | 2 | 3 | 4

Clairement, vous pouvez facilement générer cette chaîne de définition de grammaire à partir de vos deux tableaux. Introduisez ensuite cela dans le code qui génère toutes les chaînes d'une grammaire donnée, et vous avez terminé. vous aurez toutes les possibilités. (Pas nécessairement dans l'ordre dans lequel vous les voulez, remarquez.)

13
Eric Lippert

Pour comparaison, voici une façon de le faire avec Python

from itertools import product
X=["a", "b", "c"]
Y=[3, 4, 2]
terms = (["%s%s"%(x,i+1) for i in range(y)] for x,y in Zip(X,Y))
for item in product(*terms):
    print " ".join(item)
3
John La Rooy

Pour une autre solution non basée sur linq, vous pouvez utiliser:

public class CartesianProduct<T>
    {
        int[] lengths;
        T[][] arrays;
        public CartesianProduct(params  T[][] arrays)
        {
            lengths = arrays.Select(k => k.Length).ToArray();
            if (lengths.Any(l => l == 0))
                throw new ArgumentException("Zero lenght array unhandled.");
            this.arrays = arrays;
        }
        public IEnumerable<T[]> Get()
        {
            int[] walk = new int[arrays.Length];
            int x = 0;
            yield return walk.Select(k => arrays[x++][k]).ToArray();
            while (Next(walk))
            {
                x = 0;
                yield return walk.Select(k => arrays[x++][k]).ToArray();
            }

        }
        private bool Next(int[] walk)
        {
            int whoIncrement = 0;
            while (whoIncrement < walk.Length)
            {
                if (walk[whoIncrement] < lengths[whoIncrement] - 1)
                {
                    walk[whoIncrement]++;
                    return true;
                }
                else
                {
                    walk[whoIncrement] = 0;
                    whoIncrement++;
                }
            }
            return false;
        }
    }

Vous pouvez trouver un exemple sur comment l'utiliser ici .

2
Felice Pollano

Je ne suis pas disposé à vous donner le code source complet. Alors voici l'idée derrière.

Vous pouvez générer les éléments de la manière suivante:

Je suppose que A=(a1, a2, ..., an) et B=(b1, b2, ..., bn) (donc A et B contiennent chacun des éléments n).

Alors fais-le récursivement! Ecrivez une méthode qui prend une A et une B et fait votre travail:

Si A et B contiennent chacun un seul élément (appelé an resp. bn), il suffit de parcourir de 1 à bn et de concaténer an à votre variable itérative.

Si A et B contiennent chacun plus d'un élément, récupérez les premiers éléments (a1 resp b1), parcourez de 1 à bn et faites pour chaque étape d'itération:

  • appelez la méthode de manière récursive avec les sous-champs A et B à partir du deuxième élément, c.-à-d. A'=(a2, a3, ..., an) ou B'=(b2, b3, ..., bn). Pour chaque élément généré par l'appel récursif, concatérez a1, la variable itérative et l'élément généré à partir de l'appel récursif.

Ici vous pouvez trouver un exemple analogue de la génération de choses en C #, il vous suffit de "l'adapter" à vos besoins.

1
phimuemue

FinalResult est le tableau souhaité. Supposons que les deux tableaux ont la même taille.

char[] Array1 = { 'a', 'b', 'c' };
int[] Array2 = { 3, 2, 4 };

var finalResult = new List<string>();
finalResult.Add(String.Empty);
for(int i=0; i<Array1.Length; i++)
{
    var tmp = from a in finalResult
              from b in Enumerable.Range(1,Array2[i])
              select String.Format("{0} {1}{2}",a,Array1[i],b).Trim();
    finalResult = tmp.ToList();
}

Je pense que cela suffira.

1
Gulshan

Si je comprends bien, vous voulez quelque chose comme Produit cartésien Si tel est le cas, voici comment vous pouvez le faire avec LINQ. Peut-être pas une réponse exacte mais essayez de vous faire une idée


    char[] Array1 = { 'a', 'b', 'c' };
    string[] Array2 = { "10", "20", "15" };

    var result = from i in Array1
                 from j in Array2
                   select i + j;

Ces articles pourraient aider

1
Asad Butt

Voici une version javascript que je suis sûr que quelqu'un pourra convertir. Il a été testé à fond.

Voici le violon .

function combinations (Asource){

    var combos = [];
    var temp = [];

    var picker = function (arr, temp_string, collect) {
        if (temp_string.length) {
           collect.Push(temp_string);
        }

        for (var i=0; i<arr.length; i++) {
            var arrcopy = arr.slice(0, arr.length);
            var elem = arrcopy.splice(i, 1);

            if (arrcopy.length > 0) {
                picker(arrcopy, temp_string.concat(elem), collect);
            } else {
                collect.Push(temp_string.concat(elem));
            }   
        }   
    }

    picker(Asource, temp, combos);

    return combos;

}

var todo = ["a", "b", "c", "d"]; // 5 in this set
var resultingCombos = combinations (todo);
console.log(resultingCombos);
0
bob

Une autre solution non basée sur linq, plus efficace:

static IEnumerable<T[]> CartesianProduct<T>(T[][] arrays) {
    int[] lengths;
    lengths = arrays.Select(a => a.Length).ToArray();
    int Len = arrays.Length;
    int[] inds = new int[Len];
    int Len1 = Len - 1;
    while (inds[0] != lengths[0]) {
        T[] res = new T[Len];
        for (int i = 0; i != Len; i++) {
            res[i] = arrays[i][inds[i]];
        }
        yield return res;
        int j = Len1;
        inds[j]++;
        while (j > 0 && inds[j] == lengths[j]) {
            inds[j--] = 0;
            inds[j]++;
        }
    }
}
0
Peter Almazov