开发者

Elegant way to combine multiple collections of elements?

开发者 https://www.devze.com 2023-02-01 00:40 出处:网络
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

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.


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

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

Alternatively, .Union() will remove duplicate elements.


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.


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); });


Use Enumerable.Concat like so:

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


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();


Use Enumerable.Concat:

var query = foo.Concat(bar);


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.


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.


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.


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 };


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消