Une manière élégante de combiner plusieurs collections d'éléments?

94

Disons que j'ai un nombre arbitraire de collections, chacune contenant des objets du même type (par exemple, List<int> fooet List<int> bar). Si ces collections étaient elles-mêmes dans une collection (par exemple, de type List<List<int>>, je pourrais utiliser SelectManypour les combiner toutes en une seule collection.

Cependant, si ces collections ne sont pas déjà dans la même collection, j'ai l'impression que je devrais écrire une méthode comme celle-ci:

public static IEnumerable<T> Combine<T>(params ICollection<T>[] toCombine)
{
   return toCombine.SelectMany(x => x);
}

Ce que j'appellerais alors comme ceci:

var combined = Combine(foo, bar);

Existe-t-il un moyen propre et élégant de combiner (un nombre quelconque de) collections sans avoir à écrire une méthode utilitaire comme Combineci-dessus? Cela semble assez simple qu'il devrait y avoir un moyen de le faire dans LINQ, mais peut-être pas.

Donut
la source
Méfiez-vous de Concat pour concaténer un très grand nombre d'énumérables, pourrait faire exploser votre pile: programmatelyspeaking.com
...

Réponses:

107

Je pense que vous recherchez peut-être LINQ .Concat()?

var combined = foo.Concat(bar).Concat(foobar).Concat(...);

Sinon, .Union()supprimera les éléments en double.

Domenic
la source
2
Merci; la seule raison pour laquelle je n'ai pas fait cela en premier lieu est que (pour moi) cela semble devenir de plus en plus moche avec le nombre de collections à traiter. Cependant, cela présente l'avantage d'utiliser les fonctions LINQ existantes, avec lesquelles les futurs développeurs seront probablement déjà familiers.
Donut
2
Merci pour la .Union()pointe. Gardez à l'esprit que vous devrez implémenter IComparer sur votre type personnalisé au cas où il le serait.
Gonzo345
29

Pour moi Concat, une méthode d'extension n'est pas très élégante dans mon code lorsque j'ai plusieurs grandes séquences à concaténer. C'est simplement un problème d'indentation / formatage de code et quelque chose de très personnel.

Bien sûr, cela ressemble à ceci:

var list = list1.Concat(list2).Concat(list3);

Pas si lisible quand il se lit comme:

var list = list1.Select(x = > x)
   .Concat(list2.Where(x => true)
   .Concat(list3.OrderBy(x => x));

Ou quand ça ressemble à:

return Normalize(list1, a, b)
    .Concat(Normalize(list2, b, c))
       .Concat(Normalize(list3, c, d));

ou quel que soit votre formatage préféré. Les choses empirent avec des concats plus complexes. La raison de ma sorte de dissonance cognitive avec le style ci-dessus est que la première séquence se trouve en dehors de la Concatméthode alors que les séquences suivantes se trouvent à l'intérieur. Je préfère plutôt appeler la Concatméthode statique directement et non le style d'extension:

var list = Enumerable.Concat(list1.Select(x => x),
                             list2.Where(x => true));

Pour plus de nombre de concats de séquences, je porte la même méthode statique que dans OP:

public static IEnumerable<T> Concat<T>(params IEnumerable<T>[] sequences)
{
    return sequences.SelectMany(x => x);
}

Donc je peux écrire:

return EnumerableEx.Concat
(
    list1.Select(x = > x),
    list2.Where(x => true),
    list3.OrderBy(x => x)
);

Regarde mieux. Le nom de classe supplémentaire, sinon redondant, que je dois écrire n'est pas un problème pour moi étant donné que mes séquences semblent plus propres avec l' Concatappel. C'est moins un problème en C # 6 . Vous pouvez simplement écrire:

return Concat(list1.Select(x = > x),
              list2.Where(x => true),
              list3.OrderBy(x => x));

J'aurais aimé avoir des opérateurs de concaténation de liste en C #, quelque chose comme:

list1 @ list2 // F#
list1 ++ list2 // Scala

Beaucoup plus propre de cette façon.

nawfal
la source
4
J'apprécie votre souci du style de codage. C'est beaucoup plus élégant.
Bryan Rayner
Peut-être qu'une version plus petite de votre réponse peut être fusionnée avec la première réponse ici.
Bryan Rayner
2
Je aime cette mise en forme aussi bien - même si je préfère effectuer les opérations de liste individuelle (par exemple Where, OrderBy) d' abord pour plus de clarté, surtout si elles sont quelque chose de plus complexe que cet exemple.
brichins
27

Pour le cas lorsque vous faire une collection de collections, soit une List<List<T>>, Enumerable.Aggregateest une façon plus élégante de combiner toutes les listes en une seule:

var combined = lists.Aggregate((acc, list) => { return acc.Concat(list); });
Astreltsov
la source
1
Comment obtenez-vous listsici en premier? C'est le premier problème d'OP. S'il avait un collection<collection>pour commencer, SelectManyc'est bien plus simple.
nawfal
Je ne sais pas ce qui s'est passé, mais cela semble répondre à la mauvaise question. Réponse modifiée pour refléter cela. Beaucoup de gens semblent finir ici parce qu'il est nécessaire de combiner une liste <List <T>>
astreltsov
14

Utilisez Enumerable.Concatcomme ça:

var combined = foo.Concat(bar).Concat(baz)....;
Jason
la source
7

Vous pouvez toujours utiliser Aggregate combiné avec Concat ...

        var listOfLists = new List<List<int>>
        {
            new List<int> {1, 2, 3, 4},
            new List<int> {5, 6, 7, 8},
            new List<int> {9, 10}
        };

        IEnumerable<int> combined = new List<int>();
        combined = listOfLists.Aggregate(combined, (current, list) => current.Concat(list)).ToList();
drs
la source
1
La meilleure solution à ce jour.
Oleg
@Oleg SelectManyest simplement plus simple.
nawfal
6

La seule façon que je vois est d'utiliser Concat()

 var foo = new List<int> { 1, 2, 3 };
 var bar = new List<int> { 4, 5, 6 };
 var tor = new List<int> { 7, 8, 9 };

 var result = foo.Concat(bar).Concat(tor);

Mais vous devez décider de ce qui est mieux:

var result = Combine(foo, bar, tor);

ou

var result = foo.Concat(bar).Concat(tor);

Un des raisons Concat()pour lesquelles ce sera un meilleur choix est que ce sera plus évident pour un autre développeur. Plus lisible et simple.

Restuta
la source
3

Vous pouvez utiliser Union comme suit:

var combined=foo.Union(bar).Union(baz)...

Cela supprimera les éléments identiques, donc si vous en avez, vous voudrez peut-être les utiliser à la Concatplace.

Michael Goldshteyn
la source
4
Non! Enumerable.Unionest une union d'ensemble qui ne produit pas le résultat souhaité (elle ne produit des doublons qu'une seule fois).
jason
Ouais, j'ai besoin des instances spécifiques des objets, car je dois faire un traitement spécial sur eux. Utiliser intdans mon exemple peut avoir dérangé un peu les gens; mais Unionne fonctionnera pas pour moi dans ce cas.
Donut
2

Quelques techniques utilisant des initialiseurs de collection -

en supposant ces listes:

var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5, 6 };

SelectMany avec un initialiseur de tableau (pas vraiment élégant pour moi, mais ne repose sur aucune fonction d'assistance):

var combined = new []{ list1, list2 }.SelectMany(x => x);

Définissez une extension de liste pour Add qui permet IEnumerable<T>dans l' List<T> initialiseur :

public static class CollectionExtensions
{
    public static void Add<T>(this ICollection<T> collection, IEnumerable<T> items)
    {
        foreach (var item in items) collection.Add(item);
    }
}

Ensuite, vous pouvez créer une nouvelle liste contenant les éléments des autres comme celle-ci (cela permet également de mélanger des éléments uniques).

var combined = new List<int> { list1, list2, 7, 8 };
Dave Andersen
la source
1

Étant donné que vous commencez avec un tas de collections séparées, je pense que votre solution est plutôt élégante. Vous allez devoir faire quelque chose pour les assembler.

Il serait plus pratique de créer une méthode d'extension à partir de votre méthode Combine, ce qui la rendrait disponible partout où vous allez.

Dave Swersky
la source
J'aime l'idée de la méthode d'extension, je ne voulais tout simplement pas réinventer la roue si LINQ avait déjà une méthode qui ferait la même chose (et serait immédiatement compréhensible pour les autres développeurs plus tard)
Donut
1

Tout ce dont vous avez besoin est ceci, pour tout IEnumerable<IEnumerable<T>> lists:

var combined = lists.Aggregate((l1, l2) => l1.Concat(l2));

Cela combinera tous les éléments listsen un seul IEnumerable<T>(avec des doublons). Utilisez Unionau lieu de Concatpour supprimer les doublons, comme indiqué dans les autres réponses.

Nir Maoz
la source