Elegancki sposób łączenia wielu kolekcji elementów?

96

Powiedzmy, że mam dowolną liczbę kolekcji, z których każda zawiera obiekty tego samego typu (na przykład List<int> fooi List<int> bar). Gdyby te kolekcje same znajdowały się w kolekcji (np. Czcionek List<List<int>>, mógłbym użyć ich SelectManydo połączenia ich wszystkich w jedną kolekcję).

Jeśli jednak te kolekcje nie są już w tej samej kolekcji, to mam wrażenie, że musiałbym napisać taką metodę:

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

Które wtedy nazwałbym tak:

var combined = Combine(foo, bar);

Czy istnieje czysty, elegancki sposób łączenia (dowolnej liczby) kolekcji bez konieczności pisania metody użytkowej takiej jak Combinepowyżej? Wydaje się dość proste, że powinien istnieć sposób, aby to zrobić w LINQ, ale być może nie.

Pączek
źródło
Uważaj na Concat za konkatenację bardzo dużej liczby wyliczalnych, może wysadzić twój stos: programmaticallyspeaking.com/ ...
nawfal

Odpowiedzi:

109

Myślę, że możesz szukać LINQ .Concat()?

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

Alternatywnie, .Union()usunie zduplikowane elementy.

Domenic
źródło
2
Dzięki; jedynym powodem, dla którego tego nie zrobiłem, jest to, że (dla mnie) wydaje się to być brzydsze, im więcej kolekcji masz do czynienia. Ma to jednak tę zaletę, że korzysta z istniejących funkcji LINQ, które przyszli programiści prawdopodobnie już znają.
Donut
2
Dzięki za .Union()cynk. Pamiętaj, że jeśli tak jest, musisz zaimplementować IComparer na swoim typie niestandardowym.
Gonzo345
30

Dla mnie Concatmetoda rozszerzająca nie jest zbyt elegancka w moim kodzie, gdy mam wiele dużych sekwencji do połączenia. To tylko problem z wcięciami / formatowaniem kodu i coś bardzo osobistego.

Jasne, że wygląda dobrze tak:

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

Nie tak czytelne, gdy brzmi:

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

Lub gdy wygląda na to:

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

lub cokolwiek preferujesz. Sytuacja pogarsza się przy bardziej złożonych konkatacjach. Przyczyną mojego rodzaju dysonansu poznawczego z powyższym stylem jest to, że pierwsza sekwencja znajduje się poza Concatmetodą, a kolejne sekwencje wewnątrz. Wolę bezpośrednio wywoływać Concatmetodę statyczną , a nie styl rozszerzenia:

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

Dla większej liczby konkatacji sekwencji stosuję tę samą metodę statyczną co w OP:

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

Mogę więc napisać:

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

Wygląda lepiej. Dodatkowa, w przeciwnym razie zbędna nazwa klasy, którą muszę napisać, nie stanowi dla mnie problemu, biorąc pod uwagę, że moje sekwencje wyglądają bardziej czysto po Concatwywołaniu. To mniejszy problem w C # 6 . Możesz po prostu napisać:

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

Szkoda, że ​​nie mamy operatorów konkatenacji list w C #, coś takiego:

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

W ten sposób dużo czystszy.

nawfal
źródło
4
Doceniam twoją troskę o styl kodowania. To jest o wiele bardziej eleganckie.
Bryan Rayner,
Być może mniejszą wersję Twojej odpowiedzi można połączyć z pierwszą odpowiedzią tutaj.
Bryan Rayner,
2
I jak to formatowanie, a także - choć ja wolę wykonywać operacje Lista osób (np Where, OrderBy) po raz pierwszy dla jasności, zwłaszcza jeśli są one coś bardziej skomplikowane niż w tym przykładzie.
brichins
27

W przypadku, gdy robią mieć kolekcję zbiorów, czyli List<List<T>>, Enumerable.Aggregatejest bardziej elegancki sposób, aby połączyć wszystkie listy w jednym:

var combined = lists.Aggregate((acc, list) => { return acc.Concat(list); });
astreltsov
źródło
1
Jak dostaniesz się liststutaj jako pierwszy? To pierwszy problem OP. Gdyby miał collection<collection>od czego zacząć, to SelectManyjest po prostu prostsze.
nawfal
Nie jestem pewien, co się stało, ale wydaje się, że to odpowiedź na złe pytanie. Zredagowana odpowiedź, aby to odzwierciedlić. Wydaje się, że wiele osób kończy tutaj, ponieważ potrzeba połączenia listy <List <T>>
astreltsov Kwietnia
14

Użyj w ten Enumerable.Concatsposób:

var combined = foo.Concat(bar).Concat(baz)....;
Jason
źródło
7

Zawsze możesz użyć Aggregate w połączeniu z 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
źródło
1
Jak dotąd najlepsze rozwiązanie.
Oleg
@Oleg SelectManyjest po prostu prostszy.
nawfal
6

Jedyny sposób, w jaki widzę, to użycie 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);

Ale powinieneś zdecydować, co jest lepsze:

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

lub

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

Jednym z powodów Concat(), dla których będzie to lepszy wybór, jest to, że będzie to bardziej oczywiste dla innego programisty. Bardziej czytelne i proste.

Restuta
źródło
3

Możesz użyć Union w następujący sposób:

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

Spowoduje to jednak usunięcie identycznych elementów, więc jeśli je masz, możesz Concatzamiast tego użyć .

Michael Goldshteyn
źródło
4
Nie! Enumerable.Unionjest sumą zbioru, która nie daje pożądanego rezultatu (daje duplikaty tylko raz).
jason
Tak, potrzebuję konkretnych instancji obiektów, ponieważ muszę wykonać na nich specjalne przetwarzanie. Używanie intw moim przykładzie mogło trochę odstraszyć ludzi; ale Unionnie zadziała w tym przypadku.
Donut
2

Kilka technik wykorzystujących inicjatory kolekcji -

zakładając te listy:

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

SelectMany z inicjatorem tablicy (nie jest to dla mnie zbyt eleganckie, ale nie polega na żadnej funkcji pomocniczej):

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

Zdefiniuj rozszerzenie listy dla Dodaj, które pozwala IEnumerable<T>w List<T> inicjatorze :

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

Następnie możesz utworzyć nową listę zawierającą elementy innych, takie jak ta (umożliwia to również mieszanie pojedynczych elementów).

var combined = new List<int> { list1, list2, 7, 8 };
Dave Andersen
źródło
1

Biorąc pod uwagę, że zaczynasz od kilku oddzielnych kolekcji, myślę, że twoje rozwiązanie jest raczej eleganckie. Będziesz musiał coś zrobić, żeby je połączyć.

Byłoby wygodniej składniowo utworzyć metodę rozszerzającą z metody Combine, która udostępniłaby ją w dowolnym miejscu.

Dave Swersky
źródło
Podoba mi się pomysł na metodę rozszerzenia, po prostu nie chciałem wymyślać koła na nowo, gdyby LINQ miał już metodę, która robiłaby to samo (i byłaby natychmiast zrozumiała dla innych programistów w dalszej kolejności)
Donut
1

Wszystko, czego potrzebujesz, to IEnumerable<IEnumerable<T>> lists:

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

Spowoduje to połączenie wszystkich elementów w listsjeden IEnumerable<T>(z duplikatami). Użyj Unionzamiast, Concataby usunąć duplikaty, jak wskazano w innych odpowiedziach.

Nir Maoz
źródło