Elegant way to combine multiple collections of elements?

113,070

Solution 1

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

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

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

Use Enumerable.Concat like so:

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

Solution 5

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();
Share:
113,070
Donut
Author by

Donut

Mmm... donuts.

Updated on July 22, 2021

Comments

  • Donut
    Donut almost 3 years

    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.

  • jason
    jason over 13 years
    No! Enumerable.Union is a set union which does not produce the desired result (it only yields duplicates once).
  • Donut
    Donut over 13 years
    Thanks; the only reason I didn't do this in the first place is that (to me) it seems to get uglier the more collections you have to deal with. However this has the benefit of using existing LINQ functions, which future developers will likely already be familiar with.
  • Donut
    Donut over 13 years
    Yeah, I need the specific instances of the objects, as I need to do some special processing on them. Using int in my example might have thrown people off a little bit; but Union won't work for me in this case.
  • Donut
    Donut over 13 years
    I like the extension method idea, I just didn't want to reinvent the wheel if LINQ already had a method that would do the same thing (and be immediately to understandable to other developers further down the line)
  • Bryan Rayner
    Bryan Rayner about 8 years
    I appreciate your concern for coding style. This is much more elegant.
  • Bryan Rayner
    Bryan Rayner about 8 years
    Perhaps a smaller version of your answer can be merged with the top answer here.
  • brichins
    brichins over 7 years
    I like this formatting as well - although I prefer to perform the individual list operations (e.g. Where, OrderBy) first for clarity, especially if they're anything more complex than this example.
  • Clay
    Clay about 6 years
    The best solution so far.
  • nawfal
    nawfal about 5 years
    How do you get the lists here first? That's the first problem OP has. If he had a collection<collection> to start with then SelectMany is just way simpler.
  • nawfal
    nawfal about 5 years
    @Oleg SelectMany is just simpler.
  • astreltsov
    astreltsov about 5 years
    Not sure what happened, but this does seem to be answering the wrong question. Edited answer to reflect this. Many people do seem to end up here because the need to combine a List<List<T>>
  • Gonzo345
    Gonzo345 about 5 years
    Thanks for the .Union() tip. Bear in mind you will need to implement IComparer on your custom type in case it is.
  • Jay
    Jay over 2 years
    The documentation Says you have to implement IEquatable<T> while in fact you only need to override Equals and GetHashCode