Cách thanh lịch để kết hợp nhiều bộ sưu tập các yếu tố?


94

Giả sử tôi có một số bộ sưu tập tùy ý, mỗi bộ chứa các đối tượng cùng loại (ví dụ: List<int> fooList<int> bar). Nếu bản thân những bộ sưu tập này nằm trong một bộ sưu tập (ví dụ: loại List<List<int>>, tôi có thể sử dụng SelectManyđể kết hợp tất cả chúng thành một bộ sưu tập.

Tuy nhiên, nếu những bộ sưu tập này không nằm trong cùng một bộ sưu tập, thì ấn tượng của tôi là tôi phải viết một phương thức như thế này:

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

Sau đó tôi sẽ gọi như thế này:

var combined = Combine(foo, bar);

Có cách nào gọn gàng và thanh lịch để kết hợp (bất kỳ số lượng) bộ sưu tập nào mà không cần phải viết một phương thức tiện ích như Combinetrên không? Nó có vẻ đơn giản đến mức phải có một cách để làm điều đó trong LINQ, nhưng có lẽ không.



Cẩn thận với concat cho concatenating số lượng rất lớn các enumerables, có thể thổi lên ngăn xếp của bạn: programmaticallyspeaking.com/...
nawfal

Câu trả lời:


107

Tôi nghĩ bạn có thể đang tìm kiếm LINQ's .Concat()?

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

Ngoài ra, .Union()sẽ loại bỏ các phần tử trùng lặp.


2
Cảm ơn; lý do duy nhất tôi không làm điều này ngay từ đầu là (đối với tôi) nó có vẻ càng xấu hơn khi bạn phải xử lý nhiều bộ sưu tập hơn. Tuy nhiên, điều này có lợi khi sử dụng các hàm LINQ hiện có, mà các nhà phát triển trong tương lai có thể đã quen thuộc với.
Donut

2
Cảm ơn cho .Union()mẹo. Hãy nhớ rằng bạn sẽ cần phải triển khai IComparer trên loại tùy chỉnh của mình trong trường hợp đó.
Gonzo345

29

Đối với tôi Concatnhư một phương thức mở rộng không phải là rất thanh lịch trong mã của tôi khi tôi có nhiều chuỗi lớn để nối. Đây chỉ đơn thuần là vấn đề thụt lề / định dạng codde và một cái gì đó rất cá nhân.

Chắc chắn nó sẽ tốt như thế này:

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

Không thể đọc được khi nó đọc như:

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

Hoặc khi nó trông giống như:

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

hoặc bất kỳ định dạng ưa thích của bạn là gì. Mọi thứ trở nên tồi tệ hơn với những kết nối phức tạp hơn. Lý do cho tôi loại bất hòa nhận thức với phong cách trên là người đầu tiên bên ngoài chuỗi lời nói dối của Concatphương pháp trong khi trình tự tiếp theo nằm bên trong. Tôi thích gọi Concatphương thức tĩnh trực tiếp chứ không phải kiểu mở rộng:

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

Để có thêm số lượng kết hợp của chuỗi, tôi thực hiện cùng một phương thức tĩnh như trong OP:

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

Vì vậy, tôi có thể viết:

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

Trông tốt hơn. Việc bổ sung, nếu không thì thừa, tên lớp mà tôi phải viết không phải là vấn đề đối với tôi vì các trình tự của tôi trông gọn gàng hơn với lệnh Concatgọi. Nó ít có vấn đề hơn trong C # 6 . Bạn chỉ có thể viết:

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

Chúng tôi muốn chúng tôi có danh sách các toán tử nối trong C #, giống như:

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

Theo cách đó sạch hơn nhiều.


4
Tôi đánh giá cao mối quan tâm của bạn đối với phong cách mã hóa. Điều này là thanh lịch hơn nhiều.
Bryan Rayner

Có lẽ một phiên bản nhỏ hơn của câu trả lời của bạn có thể được hợp nhất với câu trả lời hàng đầu ở đây.
Bryan Rayner

2
Tôi như thế này định dạng là tốt - mặc dù tôi thích để thực hiện các hoạt động danh sách cá nhân (ví dụ Where, OrderBy) đầu tiên cho rõ ràng, đặc biệt là nếu họ đang bất cứ điều gì phức tạp hơn ví dụ này.
brichins

27

Đối với trường hợp khi bạn làm có một bộ sưu tập các bộ sưu tập, tức là một List<List<T>>, Enumerable.Aggregatelà một cách thanh lịch hơn để kết hợp tất cả danh sách thành một:

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

1
Làm thế nào để bạn nhận được listsở đây đầu tiên? Đó là vấn đề đầu tiên OP gặp phải. Nếu anh ta phải collection<collection>bắt đầu thì SelectManyđơn giản hơn.
nawfal

Không chắc điều gì đã xảy ra, nhưng điều này dường như đang trả lời câu hỏi sai. Đã chỉnh sửa câu trả lời để phản ánh điều này. Nhiều người dường như kết thúc ở đây vì sự cần thiết phải kết hợp một List <List <T >>
astreltsov



7

Bạn luôn có thể sử dụng Aggregate kết hợp với 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
Giải pháp tốt nhất cho đến nay.
Oleg

@Oleg SelectManychỉ đơn giản hơn.
nawfal

6

Cách duy nhất tôi thấy là sử dụng 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);

Nhưng bạn nên quyết định điều gì tốt hơn:

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

hoặc là

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

Một điểm tại sao Concat()sẽ là lựa chọn tốt hơn là nó sẽ rõ ràng hơn đối với một nhà phát triển khác. Dễ đọc và đơn giản hơn.


3

Bạn có thể sử dụng Union như sau:

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

Tuy nhiên, điều này sẽ loại bỏ các phần tử giống hệt nhau, vì vậy nếu bạn có những phần tử đó, bạn có thể muốn sử dụng Concatthay thế.


4
Không! Enumerable.Unionlà một liên hợp được thiết lập không tạo ra kết quả mong muốn (nó chỉ tạo ra các bản sao một lần).
jason

Vâng, tôi cần các trường hợp cụ thể của các đối tượng, vì tôi cần thực hiện một số xử lý đặc biệt trên chúng. Việc sử dụng inttrong ví dụ của tôi có thể khiến mọi người mất hứng thú một chút; nhưng Unionsẽ không hiệu quả với tôi trong trường hợp này.
Donut

2

Một số kỹ thuật sử dụng Bộ khởi tạo Bộ sưu tập -

giả sử các danh sách này:

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

SelectMany với trình khởi tạo mảng (đối với tôi không thực sự thanh lịch nhưng không dựa vào bất kỳ chức năng trợ giúp nào):

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

Xác định tiện ích mở rộng danh sách cho Thêm mà cho phép IEnumerable<T>trong trình List<T> khởi tạo :

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

Sau đó, bạn có thể tạo một danh sách mới chứa các phần tử của những phần tử khác như thế này (nó cũng cho phép các mục riêng lẻ được trộn vào).

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

1

Cho rằng bạn đang bắt đầu với một loạt các bộ sưu tập riêng biệt, tôi nghĩ rằng giải pháp của bạn khá thanh lịch. Bạn sẽ phải làm gì đó để ghép chúng lại với nhau.

Sẽ thuận tiện hơn về mặt cú pháp nếu tạo một phương thức mở rộng ra khỏi phương thức Kết hợp của bạn, phương thức này sẽ làm cho nó có sẵn ở mọi nơi bạn đến.


Tôi thích ý tưởng phương pháp khuyến nông, tôi chỉ không muốn phát minh lại bánh xe nếu LINQ đã có một phương pháp mà sẽ làm điều tương tự (và ngay lập tức để hiểu đối với các nhà phát triển khác tiếp tục xuống dòng)
Donut

1

Tất cả những gì bạn cần là cái này, cho bất kỳ IEnumerable<IEnumerable<T>> lists:

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

Điều này sẽ kết hợp tất cả các mục liststhành một IEnumerable<T>(với các bản sao). Sử dụng Unionthay vì Concatđể loại bỏ các bản sao, như đã lưu ý trong các câu trả lời khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.