Элегантный способ объединить несколько коллекций элементов?


97

Скажем, у меня есть произвольное количество коллекций, каждая из которых содержит объекты одного типа (например, 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, но, возможно, нет.



Остерегайтесь конкатенации очень большого количества перечислимых элементов, это может взорвать ваш стек: programmaticspeaking.com/…
nawfal

Ответы:


111

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

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

В качестве альтернативы .Union()удалит повторяющиеся элементы.


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

2
Спасибо за .Union()подсказку. Имейте в виду, что вам нужно будет реализовать IComparer в своем настраиваемом типе, если это так.
Gonzo345

30

Для меня 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

Не уверен, что произошло, но похоже, что это ответ на неправильный вопрос. Отредактированный ответ, чтобы отразить это. Многие люди, кажется, попадают сюда из-за необходимости объединить List <List <T>>
астрельцов



7

Вы всегда можете использовать Aggregate в сочетании с 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представляет собой объединение множеств, которое не дает желаемого результата (оно дает дубликаты только один раз).
Джейсон

Да, мне нужны конкретные экземпляры объектов, так как мне нужно провести с ними некоторую специальную обработку. Использование 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 уже был метод, который делал бы то же самое (и сразу же был понятен другим разработчикам в дальнейшем)
Donut

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.