Bien sûr. C'est un peu délicat de faire cela avec LINQ mais certainement possible en utilisant seulement les opérateurs de requête standard.
UPDATE : C'est le sujet de mon blog le lundi 28 juin 2010 Merci pour cette excellente question. Par ailleurs, un commentateur de mon blog a fait remarquer qu'il existe une requête encore plus élégante que celle que j'ai donnée. Je vais mettre à jour le code ici pour l'utiliser.
La partie délicate consiste à faire le produit cartésien d'un nombre arbitraire de séquences. "Zipper" les lettres est trivial comparé à cela. Vous devriez étudier ceci pour vous assurer que vous comprenez comment cela fonctionne. Chaque partie est assez simple, mais il faut s'habituer à la façon dont elles sont combinées :
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, il faut d'abord comprendre ce que fait l'opération "accumuler". L'opération d'accumulation la plus simple consiste à "additionner tout ce qui se trouve dans cette séquence". Pour ce faire, il faut commencer 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 précédente et de l'élément actuel, nous accumulons le produit cartésien au fur et à mesure.
La façon dont nous allons le faire est de profiter 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 de manière répétée le produit cartésien de l'accumulateur avec l'élément suivant de la séquence d'entrée et en faisant un petit collage des résultats, nous pouvons générer le produit cartésien au fur et à mesure.
Pensez donc à la valeur de l'accumulateur. À des fins d'illustration, je vais montrer que la valeur de l'accumulateur est la suivante résultats des opérateurs de séquence qu'il contient. Ce n'est pas ce que l'accumulateur en fait contient. Ce que l'accumulateur contient réellement est le opérateurs qui produisent ces résultats. Toute l'opération ici ne fait qu'accumuler massif arbre d'opérateurs de séquence, dont le résultat est le produit cartésien. Mais le produit cartésien final lui-même n'est pas réellement calculé avant l'exécution de la requête. A titre d'illustration, je vais montrer ce que le résultats sont à chaque étape du parcours, mais n'oubliez pas qu'il s'agit en réalité de la 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 comme une séquence contenant une séquence vide : { { } }
Lors de la première accumulation, l'accumulateur est { { } } et l'élément est {1, 2}. On fait ça :
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}
. Par conséquent, nous avons {{1}, {2}}
comme résultat.
Donc, lors de la deuxième accumulation, l'accumulateur est {{1}, {2}}
et l'article est {3, 4}
. Encore une fois, on calcule 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éner le second sur le premier. Le résultat est donc la séquence {{1, 3}, {1, 4}, {2, 3}, {2, 4}}
et c'est ce que nous voulons.
Maintenant, nous accumulons à nouveau. Nous prenons le produit cartésien de l'accumulateur avec {5, 6}
pour obtenir
{({ 1, 3}, 5), ({1, 3}, 6), ({1, 4}, 5), ...
et ensuite concaténer le deuxième élément sur le premier pour obtenir :
{{1, 3, 5}, {1, 3, 6}, {1, 4, 5}, {1, 4, 6} ... }
et on a fini. Nous avons accumulé le produit cartésien.
Maintenant que nous avons une fonction d'utilité qui peut prendre le produit cartésien d'un nombre arbitraire de séquences, le reste est facile en 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 de caractères, une séquence de chaînes de caractères par ligne :
foreach (var line in result)
{
foreach (var s in line)
Console.Write(s);
Console.WriteLine();
}
C'est facile !
4 votes
Pouvez-vous donner un meilleur exemple, en utilisant moins d'éléments, et produire les résultats complets ? Par exemple, je me demande si chaque élément du premier tableau doit être apparié uniquement avec l'élément correspondant du second tableau, ou si vous voulez le combiner avec tous les éléments du second tableau.
0 votes
Probablement que la taille des tableaux est la même.
0 votes
Oui 2 tableaux sont de même taille
8 votes
Eric a fait un blog sur ce sujet juste pour vous :) blogs.msdn.com/b/ericlippert/archive/2010/06/28/