Elegant way to combine multiple collections of elements?

C#LinqGenericsCollections

C# Problem Overview


Say I have an arbitrary number of collections, each containing objects of the same type (for example, List<int> foo and List<int> bar). If these collections were themselves in a collection (e.g., of type List<List<int>>, I could use SelectMany to combine them all into one collection.

However, if these collections are not already in the same collection, it's my impression that I'd have to write a method like this:

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

Which I'd then call like this:

var combined = Combine(foo, bar);

Is there a clean, elegant way to combine (any number of) collections without having to write a utility method like Combine above? It seems simple enough that there should be a way to do it in LINQ, but perhaps not.

C# Solutions


Solution 1 - C#

I think you might be looking for LINQ's .Concat()?

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

Alternatively, .Union() will remove duplicate elements.

Solution 2 - C#

To me Concat as an extension method is not very elegant in my code when I have multiple large sequences to concat. This is merely a codde indentation/formatting problem and something very personal.

Sure it looks good like this:

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

Not so readable when it reads like:

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

Or when it looks like:

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

or whatever your preferred formatting is. Things get worse with more complex concats. The reason for my sort of cognitive dissonance with the above style is that the first sequence lie outside of the Concat method whereas the subsequent sequences lie inside. I rather prefer to call the static Concat method directly and not the extension style:

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

For more number of concats of sequences I carry the same static method as in OP:

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

So I can write:

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

Looks better. The extra, otherwise redundant, class name I have to write is not a problem for me considering my sequences look cleaner with the Concat call. It's less of a problem in C# 6. You can just write:

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

Wished we had list concatenation operators in C#, something like:

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

Much cleaner that way.

Solution 3 - C#

For the case when you do have a collection of collections, i.e. a List<List<T>>, Enumerable.Aggregate is a more elegant way to combine all lists into one:

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

Solution 4 - C#

Use Enumerable.Concat like so:

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

Solution 5 - C#

You could always use Aggregate combined with 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();

Solution 6 - C#

Use Enumerable.Concat:

var query = foo.Concat(bar);

Solution 7 - C#

The only way I see is to use 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);

But you should decide what is better:

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

or

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

One point why Concat() will be a better choice is that it will be more obvious for another developer. More readable and simple.

Solution 8 - C#

You can use Union as follows:

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

This will remove identical elements, though, so if you have those, you might want to use Concat, instead.

Solution 9 - C#

A couple techniques using Collection Initializers --

assuming these lists:

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

SelectMany with an array initializer (not really that elegant to me, but doesn't rely on any helper function):

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

Define a list extension for Add which allows IEnumerable<T> in the 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);
    }
}

Then you can create a new list containing the elements of the others like this (it also allows single items to be mixed in).

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

Solution 10 - C#

Given that you're starting with a bunch of separate collections, I think your solution is rather elegant. You're going to have to do something to stitch them together.

It would be more convenient syntactically to make an extension method out of your Combine method, which would make it available anywhere you go.

Solution 11 - C#

All you need is this, for any IEnumerable<IEnumerable<T>> lists :

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

This will combine all the items in lists into one IEnumerable<T> (with duplicates). Use Union instead of Concat to remove duplicates, as noted in the other answers.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDonutView Question on Stackoverflow
Solution 1 - C#DomenicView Answer on Stackoverflow
Solution 2 - C#nawfalView Answer on Stackoverflow
Solution 3 - C#astreltsovView Answer on Stackoverflow
Solution 4 - C#jasonView Answer on Stackoverflow
Solution 5 - C#drsView Answer on Stackoverflow
Solution 6 - C#Ahmad MageedView Answer on Stackoverflow
Solution 7 - C#RestutaView Answer on Stackoverflow
Solution 8 - C#Michael GoldshteynView Answer on Stackoverflow
Solution 9 - C#Dave AndersenView Answer on Stackoverflow
Solution 10 - C#Dave SwerskyView Answer on Stackoverflow
Solution 11 - C#Nir MaozView Answer on Stackoverflow