Cara elegan untuk menggabungkan banyak koleksi elemen?


97

Katakanlah saya memiliki jumlah koleksi yang berubah-ubah, masing-masing berisi objek dengan jenis yang sama (misalnya, List<int> foodan List<int> bar). Jika koleksi-koleksi ini adalah koleksi mereka sendiri (misalnya, dari tipe List<List<int>>, saya dapat menggunakan SelectManyuntuk menggabungkan semuanya menjadi satu koleksi.

Namun, jika koleksi ini belum ada dalam koleksi yang sama, kesan saya bahwa saya harus menulis metode seperti ini:

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

Yang kemudian saya sebut seperti ini:

var combined = Combine(foo, bar);

Adakah cara yang bersih dan elegan untuk menggabungkan (sejumlah) koleksi tanpa harus menulis metode utilitas seperti di Combineatas? Tampaknya cukup sederhana sehingga harus ada cara untuk melakukannya di LINQ, tetapi mungkin tidak.



Waspadalah terhadap Concat karena menggabungkan sejumlah besar enumerable, dapat meledakkan tumpukan Anda: programmaticallyspeaking.com/…
nawfal

Jawaban:


111

Saya pikir Anda mungkin mencari LINQ .Concat()?

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

Atau, .Union()akan menghapus elemen duplikat.


2
Terima kasih; satu-satunya alasan saya tidak melakukan ini pada awalnya adalah karena (bagi saya) tampaknya semakin jelek semakin banyak koleksi yang harus Anda tangani. Namun ini memiliki keuntungan menggunakan fungsi LINQ yang sudah ada, yang kemungkinan besar sudah dikenal oleh pengembang di masa mendatang.
Donat

2
Terima kasih atas .Union()tipnya. Ingatlah bahwa Anda perlu menerapkan IComparer pada jenis kustom Anda jika memang demikian.
Gonzo345

30

Bagi saya Concatsebagai metode ekstensi tidak terlalu elegan dalam kode saya ketika saya memiliki beberapa urutan besar untuk digabungkan. Ini hanyalah masalah lekukan / pemformatan kode dan sesuatu yang sangat pribadi.

Tentu itu terlihat bagus seperti ini:

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

Tidak begitu terbaca ketika berbunyi seperti:

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

Atau jika terlihat seperti:

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

atau apapun format pilihan Anda. Segalanya menjadi lebih buruk dengan concat yang lebih kompleks. Alasan saya jenis disonansi kognitif dengan gaya di atas adalah bahwa urutan pertama berada di luar Concatmetode sedangkan urutan berikutnya terletak di dalam. Saya lebih suka memanggil Concatmetode statis secara langsung dan bukan gaya ekstensi:

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

Untuk lebih banyak jumlah rangkaian urutan, saya membawa metode statis yang sama seperti di OP:

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

Jadi saya bisa menulis:

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

Terlihat lebih baik. Nama kelas ekstra, jika tidak berlebihan, yang harus saya tulis bukan masalah bagi saya mengingat urutan saya terlihat lebih bersih dengan Concatpanggilan tersebut. Ini bukan masalah di C # 6 . Anda tinggal menulis:

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

Berharap kami memiliki daftar operator penggabungan di C #, seperti:

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

Jauh lebih bersih.


4
Saya menghargai perhatian Anda pada gaya pengkodean. Ini jauh lebih elegan.
Bryan Rayner

Mungkin versi yang lebih kecil dari jawaban Anda dapat digabungkan dengan jawaban teratas di sini.
Bryan Rayner

2
Saya suka pemformatan ini juga - meskipun saya lebih suka melakukan operasi daftar individual (misalnya Where, OrderBy) terlebih dahulu untuk kejelasan, terutama jika ada yang lebih kompleks daripada contoh ini.
Brichins

27

Untuk kasus ketika Anda memang memiliki koleksi koleksi, yaitu a List<List<T>>, Enumerable.Aggregateadalah cara yang lebih elegan untuk menggabungkan semua daftar menjadi satu:

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

1
Bagaimana Anda bisa sampai di listssini dulu? Itulah masalah pertama yang dimiliki OP. Jika dia harus collection<collection>memulainya, itu SelectManyjauh lebih sederhana.
nawfal

Tidak yakin apa yang terjadi, tapi ini sepertinya menjawab pertanyaan yang salah. Jawaban yang diedit untuk mencerminkan ini. Banyak orang yang tampaknya berakhir di sini karena kebutuhan untuk menggabungkan List <List <T>>
astreltsov



7

Anda selalu bisa menggunakan Agregat yang dikombinasikan dengan 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();

1
Solusi terbaik sejauh ini.
Oleg

@Oleg SelectManylebih sederhana.
nawfal

6

Satu-satunya cara yang saya lihat adalah menggunakan 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);

Tetapi Anda harus memutuskan mana yang lebih baik:

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

atau

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

Satu poin mengapa Concat()akan menjadi pilihan yang lebih baik adalah bahwa itu akan lebih jelas bagi pengembang lain. Lebih mudah dibaca dan sederhana.


3

Anda dapat menggunakan Union sebagai berikut:

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

Ini akan menghapus elemen yang identik, jadi jika Anda memilikinya, Anda mungkin ingin menggunakannya Concat.


4
Tidak! Enumerable.Unionadalah gabungan himpunan yang tidak menghasilkan hasil yang diinginkan (hanya menghasilkan duplikat sekali).
jason

Ya, saya memerlukan contoh objek tertentu, karena saya perlu melakukan beberapa pemrosesan khusus pada mereka. Menggunakan intdalam contoh saya mungkin telah membuat orang sedikit kecewa; tetapi Uniontidak akan berhasil untuk saya dalam kasus ini.
Donat

2

Beberapa teknik menggunakan Collection Initializers -

dengan asumsi daftar ini:

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

SelectMany dengan penginisialisasi array (tidak terlalu elegan bagi saya, tetapi tidak bergantung pada fungsi pembantu):

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

Tentukan ekstensi daftar untuk Tambah yang memungkinkan IEnumerable<T>dalam List<T> initializer :

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

Kemudian Anda dapat membuat daftar baru yang berisi elemen-elemen lain seperti ini (ini juga memungkinkan satu item untuk dicampur).

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

1

Mengingat Anda memulai dengan sekumpulan koleksi terpisah, saya pikir solusi Anda agak elegan. Anda harus melakukan sesuatu untuk menyatukannya.

Akan lebih mudah secara sintaksis untuk membuat metode ekstensi dari metode Gabungkan Anda, yang akan membuatnya tersedia di mana pun Anda pergi.


Saya suka ide metode ekstensi, saya hanya tidak ingin menemukan kembali roda jika LINQ sudah memiliki metode yang akan melakukan hal yang sama (dan segera dapat dimengerti oleh pengembang lain di masa mendatang)
Donat

1

Yang Anda butuhkan hanyalah ini, untuk semua IEnumerable<IEnumerable<T>> lists:

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

Ini akan menggabungkan semua item listsmenjadi satu IEnumerable<T>(dengan duplikat). Gunakan Uniondaripada Concatmenghapus duplikat, seperti yang dicatat dalam jawaban lain.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.