Елегантний спосіб поєднати кілька колекцій елементів?


94

Скажімо, у мене є довільна кількість колекцій, кожна з яких містить об’єкти одного типу (наприклад, List<int> fooі List<int> bar). Якби ці колекції самі були в колекції (наприклад, типу List<List<int>>, я міг SelectManyби об'єднати їх усі в одну колекцію.

Однак, якщо ці колекції вже не в одній колекції, я складаю враження, що мені довелося б написати такий метод:

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

Який би я тоді назвав так:

var combined = Combine(foo, bar);

Чи є чистий, елегантний спосіб поєднати (будь-яку кількість) колекцій без необхідності писати корисний метод, як Combineвище? Здається досить простим, що це має бути спосіб зробити це в LINQ, але, можливо, ні.



Остерігайтеся Concat для об'єднання дуже великої кількості перерахованих, може підірвати ваш стек: programmaticallyspeaking.com/…
nawfal

Відповіді:


107

Я думаю, ви можете шукати LINQ .Concat()?

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

Або ж .Union()видалить повторювані елементи.


2
Дякую; єдина причина, чому я не зробив цього в першу чергу, полягає в тому, що (для мене) це здається потворнішим, чим більше колекцій доводиться мати справу. Однак це має перевагу використання існуючих функцій LINQ, з якими майбутні розробники, ймовірно, вже знайомі.
Пончик

2
Дякую за .Union()підказку. Майте на увазі, що вам потрібно буде застосувати IComparer до власного типу, якщо він є.
Gonzo345

29

Для мене Concatяк метод розширення не дуже елегантний у моєму коді, коли у мене є кілька великих послідовностей для concat. Це просто проблема з відступами / форматуваннями кодів і щось дуже особисте.

Звичайно, це виглядає так:

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

Не так читабельно, коли читається так:

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

Або коли це виглядає так:

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

або будь-яке ваше бажане форматування. З більш складними конкатами справи погіршуються. Причиною мого різновиду когнітивного дисонансу з вищезазначеним стилем є те, що перша послідовність лежить поза Concatметодом, тоді як наступні послідовності лежать всередині. Я радше вважаю за краще викликати статичний Concatметод безпосередньо, а не стиль розширення:

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

Для більшої кількості конкатів послідовностей я використовую той самий статичний метод, що і в OP:

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

Тож я можу написати:

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

Виглядає краще. Додаткове, в іншому випадку зайве, ім'я класу, яке я повинен написати, не є проблемою для мене, враховуючи, що мої послідовності виглядають чистішими під час Concatдзвінка. Це менша проблема в C # 6 . Ви можете просто написати:

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

Хотілося б, щоб ми мали оператори конкатенації списків у C #, приблизно так:

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

Так набагато чистіше.


4
Я вдячний вашій турботі про стиль кодування. Це набагато елегантніше.
Брайан Рейнер

Можливо, меншу версію вашої відповіді можна об’єднати з верхньою відповіддю тут.
Брайан Рейнер,

2
Мені також подобається це форматування - хоча я віддаю перевагу виконувати окремі операції зі списком (наприклад Where, OrderBy) спочатку для наочності, особливо якщо вони щось складніше, ніж цей приклад.
brichins

27

Для випадку , коли ви робите є колекція колекцій, тобто List<List<T>>, Enumerable.Aggregateбільш елегантний спосіб об'єднати всі списки в одному:

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

1
Як ти listsспочатку потрапляєш сюди? Це перша проблема OP. Якщо у нього було collection<collection>для початку, SelectManyце набагато простіше.
nawfal

Не впевнений, що сталося, але, схоже, це відповідає на неправильне запитання. Відредагована відповідь, щоб відобразити це. Багато людей, здається, опиняються тут, тому що необхідність об'єднати Список <Список <T>>
астрельцов



7

Ви завжди можете використовувати агрегат у поєднанні з 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
Найкраще рішення поки що.
Олег

@Oleg SelectManyпросто простіше.
nawfal

6

Єдиний спосіб, який я бачу - це використовувати 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);

Але вам слід вирішити, що краще:

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

або

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

Одним моментом, чому Concat()буде кращий вибір, є те, що це буде очевидніше для іншого розробника. Більш читабельним і простим.


3

Ви можете використовувати Union таким чином:

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

Однак це призведе до видалення однакових елементів, тому, якщо у вас є такі, ви можете Concatзамість них використовувати .


4
Немає! Enumerable.Unionє набором об'єднань, який не дає бажаного результату (він дає лише дублікати один раз).
jason

Так, мені потрібні конкретні екземпляри об’єктів, оскільки мені потрібно зробити спеціальну обробку над ними. Використання intв моєму прикладі могло трохи відкинути людей; але Unionв цьому випадку не спрацює для мене.
Пончик

2

Кілька прийомів з використанням ініціалізаторів збору -

припускаючи ці списки:

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

SelectMany з ініціалізатором масиву (не дуже елегантний для мене, але не покладається на жодну допоміжну функцію):

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

Визначте розширення списку для Add, яке дозволяє IEnumerable<T>в List<T> ініціалізаторі :

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

Потім ви можете створити новий список, що містить елементи інших, подібних до цього (він також дозволяє змішувати окремі елементи).

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

1

Враховуючи, що ви починаєте з купи окремих колекцій, я думаю, що ваше рішення досить елегантне. Вам доведеться щось зробити, щоб з’єднати їх.

Було б зручніше синтаксично зробити метод розширення з вашого методу Combine, який зробив би його доступним у будь-якому місці.


Мені подобається ідея методу розширення, я просто не хотів винаходити колесо, якщо LINQ вже мав метод, який робив би те саме (і був би відразу зрозумілим для інших розробників далі)
Пончик

1

Все, що вам потрібно, це для будь-якого IEnumerable<IEnumerable<T>> lists:

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

Це об’єднає всі елементи в listsодне IEnumerable<T>(з дублікатами). Використовуйте Unionзамість того, Concatщоб видаляти дублікати, як зазначено в інших відповідях.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.