web-dev-qa-db-fra.com

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

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

Cependant, si ces collections ne sont pas déjà dans la même collection, j'ai l'impression qu'il me faudrait é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 simple et élégant de combiner un nombre quelconque de collections sans avoir à écrire une méthode utilitaire telle que Combine ci-dessus? Il semble assez simple qu'il y ait un moyen de le faire dans LINQ, mais peut-être pas.

67
Donut

Je pense que vous pourriez être à la recherche de .Concat() ?

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

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

78
Domenic

Pour moi, Concat en tant que méthode d'extension n'est pas très élégant dans mon code lorsque j'ai plusieurs séquences volumineuses à concaténer. Il s’agit simplement d’un problème d’indentation/de formatage et de quelque chose de très personnel.

Bien sûr, ça ressemble à ça:

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

Pas si lisible quand ça 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 quelle que soit votre mise en forme préférée. Les choses s'aggravent 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 situe en dehors de la méthode Concat alors que les séquences suivantes se trouvent à l'intérieur. Je préfère plutôt appeler la méthode statique Concat directement et non le style d'extension:

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

Pour plus de nombres de séquences 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 ont l’air plus propres avec l’appel Concat. C’est moins un problème dans C # 6 . Vous pouvez simplement écrire:

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

Souhait que nous ayons des opérateurs de concaténation de listes en C #, quelque chose comme:

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

Beaucoup plus propre de cette façon.

20
nawfal

Enumerable.Aggregate est la bonne façon "élégante" de le faire:

var all = lists.Aggregate((acc, list) => { return acc.Concat(list); });
17
astreltsov

Utilisez Enumerable.Concat like so:

var combined = foo.Concat(bar).Concat(baz)....;
14
jason

Utilisez Enumerable.Concact :

var query = foo.Concat(bar);
7
Ahmad Mageed

Le seul moyen 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 devriez décider de ce qui est mieux:

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

ou

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

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

6
Restuta

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();
6
drs

Vous pouvez utiliser Union comme suit:

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

Cela supprimera les éléments identiques, cependant, si vous en avez, vous pouvez utiliser plutôt Concat.

2
Michael Goldshteyn

Étant donné que vous commencez avec un ensemble 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 syntaxiquement de créer une méthode d’extension à partir de votre méthode Combiner, afin de la rendre disponible où que vous soyez.

1
Dave Swersky

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

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

Cela combinera tous les éléments de lists en un IEnumerable<T> (avec des doublons). Utilisez Union au lieu de Concat pour supprimer les doublons, comme indiqué dans les autres réponses.

0
Nir Maoz