Elegante Möglichkeit, mehrere Sammlungen von Elementen zu kombinieren?

94

Angenommen, ich habe eine beliebige Anzahl von Sammlungen, die jeweils Objekte desselben Typs enthalten (z. B. List<int> foound List<int> bar). Wenn diese Sammlungen selbst in einer Sammlung wären (z. B. vom Typ List<List<int>>, könnte ich SelectManysie alle zu einer Sammlung kombinieren.

Wenn sich diese Sammlungen jedoch nicht bereits in derselben Sammlung befinden, hätte ich den Eindruck, dass ich eine Methode wie diese schreiben müsste:

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

Was ich dann so nennen würde:

var combined = Combine(foo, bar);

Gibt es eine saubere und elegante Möglichkeit, (beliebig viele) Sammlungen zu kombinieren, ohne eine Dienstprogrammmethode wie Combineoben schreiben zu müssen ? Es scheint einfach genug, dass es eine Möglichkeit geben sollte, dies in LINQ zu tun, aber vielleicht auch nicht.

Krapfen
quelle
Vorsicht vor Concat für die Verkettung einer sehr großen Anzahl von Enumerables, könnte Ihren Stack in die Luft
jagen

Antworten:

107

Ich denke, Sie suchen vielleicht nach LINQs .Concat()?

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

Alternativ .Union()werden doppelte Elemente entfernt.

Domenic
quelle
2
Vielen Dank; Der einzige Grund, warum ich das überhaupt nicht getan habe, ist, dass es (für mich) hässlicher zu werden scheint, je mehr Sammlungen Sie bearbeiten müssen. Dies hat jedoch den Vorteil, dass vorhandene LINQ-Funktionen verwendet werden, mit denen zukünftige Entwickler wahrscheinlich bereits vertraut sein werden.
Donut
2
Danke für den .Union()Tipp. Denken Sie daran, dass Sie IComparer auf Ihrem benutzerdefinierten Typ implementieren müssen , falls dies der Fall ist.
Gonzo345
29

Für mich ist Concateine Erweiterungsmethode in meinem Code nicht sehr elegant, wenn ich mehrere große Sequenzen zu konkatieren habe. Dies ist lediglich ein Codde-Einrückungs- / Formatierungsproblem und etwas sehr Persönliches.

Sicher sieht es so gut aus:

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

Nicht so lesbar, wenn es lautet wie:

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

Oder wenn es so aussieht:

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

oder was auch immer Ihre bevorzugte Formatierung ist. Bei komplexeren Konkaten wird es noch schlimmer. Der Grund für meine Art von kognitiver Dissonanz mit dem obigen Stil ist, dass die erste Sequenz außerhalb der ConcatMethode liegt, während die nachfolgenden Sequenzen innerhalb liegen. Ich rufe lieber die statische ConcatMethode direkt auf und nicht den Erweiterungsstil:

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

Für mehr Anzahl von Konkaten von Sequenzen verwende ich die gleiche statische Methode wie in OP:

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

So kann ich schreiben:

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

Sieht besser aus. Der zusätzliche, ansonsten redundante Klassenname, den ich schreiben muss, ist für mich kein Problem, da meine Sequenzen mit dem ConcatAufruf sauberer aussehen . In C # 6 ist das weniger problematisch . Sie können einfach schreiben:

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

Ich wünschte, wir hätten Listenverkettungsoperatoren in C #, so etwas wie:

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

So viel sauberer.

nawfal
quelle
4
Ich schätze Ihre Sorge um den Codierungsstil. Das ist viel eleganter.
Bryan Rayner
Möglicherweise kann eine kleinere Version Ihrer Antwort mit der obersten Antwort hier zusammengeführt werden.
Bryan Rayner
2
Ich mag diese auch die Formatierung - obwohl ich lieber die einzelnen Listenoperationen (zB auszuführen Where, OrderBy) zunächst für Klarheit, vor allem , wenn sie sind etwas komplexer als dieses Beispiel.
Brichins
27

Für den Fall , wenn Sie tun eine Sammlung von Sammlungen, dh ein List<List<T>>, Enumerable.Aggregateist ein eleganter Weg , um alle Listen zu einem kombinieren:

var combined = lists.Aggregate((acc, list) => { return acc.Concat(list); });
astreltsov
quelle
1
Wie kommt man listszuerst hierher? Das ist das erste Problem, das OP hat. Wenn er am Anfang einen hatte collection<collection>, dann SelectManyist es einfach viel einfacher.
Nawfal
Ich bin mir nicht sicher, was passiert ist, aber dies scheint die falsche Frage zu beantworten. Die Antwort wurde bearbeitet, um dies widerzuspiegeln. Viele Leute scheinen hier zu landen, weil die Notwendigkeit besteht, eine Liste <Liste <T >>
astreltsov
14

Verwenden Sie Enumerable.Concatwie folgt:

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

Sie können Aggregate immer in Kombination mit 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
quelle
1
Die bisher beste Lösung.
Oleg
@Oleg SelectManyist einfach einfacher.
Nawfal
6

Der einzige Weg, den ich sehe, ist zu verwenden 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);

Aber Sie sollten entscheiden, was besser ist:

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

oder

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

Ein Punkt, warum Concat()eine bessere Wahl sein wird, ist, dass es für einen anderen Entwickler offensichtlicher sein wird. Lesbarer und einfacher.

Restuta
quelle
3

Sie können Union wie folgt verwenden:

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

Dadurch werden jedoch identische Elemente entfernt. Wenn Sie diese haben, möchten Sie möglicherweise Concatstattdessen verwenden.

Michael Goldshteyn
quelle
4
Nein! Enumerable.Unionist eine Mengenvereinigung, die nicht das gewünschte Ergebnis liefert (sie liefert nur einmal Duplikate).
Jason
Ja, ich brauche die spezifischen Instanzen der Objekte, da ich eine spezielle Verarbeitung für sie durchführen muss. Die Verwendung intin meinem Beispiel hätte die Leute ein wenig abschrecken können. aber Unionfür mich in diesem Fall nicht funktionieren.
Donut
2

Ein paar Techniken mit Collection Initializers -

unter der Annahme dieser Listen:

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

SelectMany mit einem Array-Initialisierer (für mich nicht wirklich so elegant, aber auf keine Hilfsfunktion angewiesen):

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

Definieren Sie eine Listenerweiterung für Hinzufügen, die IEnumerable<T>im List<T> Initialisierer Folgendes zulässt :

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

Anschließend können Sie eine neue Liste erstellen, die die Elemente der anderen enthält (dies ermöglicht auch das Einmischen einzelner Elemente).

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

Angesichts der Tatsache, dass Sie mit einer Reihe separater Sammlungen beginnen, finde ich Ihre Lösung ziemlich elegant. Sie müssen etwas tun , um sie zusammenzunähen.

Syntaktisch wäre es bequemer, aus Ihrer Combine-Methode eine Erweiterungsmethode zu machen, die sie überall verfügbar macht.

Dave Swersky
quelle
Ich mag die Idee der Erweiterungsmethode, ich wollte das Rad einfach nicht neu erfinden, wenn LINQ bereits eine Methode hatte, die das Gleiche tut (und für andere Entwickler später sofort verständlich ist)
Donut
1

Alles was Sie brauchen ist Folgendes IEnumerable<IEnumerable<T>> lists:

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

Dadurch werden alle Elemente listszu einem zusammengefasst IEnumerable<T>(mit Duplikaten). Verwenden Sie Unionstatt Concat, um Duplikate zu entfernen, wie in den anderen Antworten angegeben.

Nir Maoz
quelle