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


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.