Amy
Amy

Reputation: 154

Merging multiple lists

Given that there I have a list of lists, List<List<T>>, in which all lists contain 0 or more items, likely, but not necessarily all the same number. And I would like tho have a list containing all the items in the Lists... but I want the order to be as follows: first the first items off all the lists, in order that they appear in the 'super-list'.

Ex.

List[0] = { 'Apple', 'Blueberry', 'Cranberry' }
List[1] = { 'Anteater', 'Baboon', 'Camel', 'Dodo'}
List[2] = { 'Albatross', 'Blackbird', 'Chicken'}

result = { 'Apple', 'Anteater', 'Albatross', 'Blueberry', 'Baboon', 
           'Blackbird', 'Cranberry', 'Camel', 'Chicken', 'Dodo' }

(note that this is NOT alphabetical order, Blueberry comes before Baboon)

Ofcourse, i could just loop through the 'superlist' with a counter, adding items to the resultslist one by one, as long as there is a list that isn't empty:

int i = 0;
bool done = false;

while (!done)
{
    bool found = false;

    foreach (list l in superlist)
    {
        if (l.Count() > i)
        {
           found = true;
           result.Add(l[i]);
        }
    }

    i++;

    if (!found)
        done = true;
}

But it would be way nicer to use some optimized LINQ function to do this. I have been looking into Zip, GroupBy and Aggregate, but couldn't get them to work.

So: Is there a pretty LINQ function, or combination of multiple, that would turn this into pretty code, or should I stick by (and maybe optimize) my current function?

Edit: A simple SelectMany(x => x) also doesn't do the trick, as it preserves the order of the lists, instead of folding them, like my algorithm does. See my initial question for more details.

Upvotes: 3

Views: 416

Answers (7)

Ivan Stoev
Ivan Stoev

Reputation: 205849

I know I'm late to the party, but since this post has been referenced from another post I was working on as a base for solving the problem, I feel it's necessary to add something to the subject.

There are two conflicting statements in the question:

But it would be way nicer to use some optimized LINQ function to do this.

and then

So: Is there a pretty LINQ function, or combination of multiple, that would turn this into pretty code, or should I stick by (and maybe optimize) my current function?

There is a big difference between optimized and pretty. Of course it depends of what does "optimized" mean. Pretty, readable, shorter code might be considered optimized for maintenance, but most of the time not for performance. So let speak about performance. The accepted answer might look cool, but is inefficient from both time and space (due to group by), and does not even provide the deferred execution behavior like the similar "standard LINQ" Concat function.

In that respect, the original function pprovided by the OP is much better. But we can go further and create a more generic function (not requiring lists) which does all this effectively and at the same time following the LINQ implementation spirit:

static class Extensions
{
    public static IEnumerable<T> Merge<T>(this IEnumerable<IEnumerable<T>> source)
    {
        var queue = new Queue<IEnumerator<T>>();
        IEnumerator<T> itemEnumerator = null;
        try
        {
            // First pass: yield the first element (if any) and schedule the next (if any)
            foreach (var list in source)
            {
                itemEnumerator = list.GetEnumerator();
                if (!itemEnumerator.MoveNext())
                    itemEnumerator.Dispose();
                else
                {
                    yield return itemEnumerator.Current;
                    if (itemEnumerator.MoveNext())
                        queue.Enqueue(itemEnumerator);
                    else
                        itemEnumerator.Dispose();
                }
            }
            // Second pass: yield the current element and schedule the next (if any)
            while (queue.Count > 0)
            {
                itemEnumerator = queue.Dequeue();
                yield return itemEnumerator.Current;
                if (itemEnumerator.MoveNext())
                    queue.Enqueue(itemEnumerator);
                else
                    itemEnumerator.Dispose();
            }
        }
        finally
        {
            if (itemEnumerator != null) itemEnumerator.Dispose();
            while (queue.Count > 0) queue.Dequeue().Dispose();
        }
    }
}

Note that I've could easily make it shorter by eliminating the repetitive parts and merging the two passes, but that would make it less readable and not faster (in fact it would be a little bit slower due to some additional checks needed).

To conclude: Library (reusable) code does not have to be (and is not in the most cases) cool or short. If it can, fine, but that should not be the driver.

Upvotes: 1

It&#39;sNotALie.
It&#39;sNotALie.

Reputation: 22814

I see what you're trying to do:

var result = input.SelectMany(l => l.Select((o, i) => new { o, i })).GroupBy(o => o.i).OrderBy(g => g.Key).SelectMany(g => g.Select(o => o.o);

Upvotes: 0

I4V
I4V

Reputation: 35363

You need SelectMany

var result = lists.SelectMany(x => x.Select((s, inx) => new { s, inx }))
                .GroupBy(x => x.inx)
                .SelectMany(x => x.Select(y => y.s))
                .ToList();

EDIT

For those, who want to try, the initialization code.

List<List<string>> lists = new List<List<string>>()
        {
            new List<string>(){ "Apple", "Blueberry", "Cranberry" },
            new List<string>(){ "Anteater", "Baboon", "Camel", "Dodo"},
            new List<string>(){ "Albatross", "Blackbird", "Chicken"},
        };

EDIT 2

OUTPUT: Apple,Anteater,Albatross,Blueberry,Baboon,Blackbird,Cranberry,Camel,Chicken,Dodo

Upvotes: 8

Jozef Benikovsk&#253;
Jozef Benikovsk&#253;

Reputation: 1141

There is no easy or optimal (by the means of performance and code readability) way how to select the first element from all the lists, then the second, etc. with built-in linq extensions. However you could build your own extension from your code or if you don't like while loops as shown below:

public static class MyLinqExtensions
{
    public static void MySelect<T>(this IEnumerable<IEnumerable<T>> superlist)
    {
        int index = 0;

        foreach (IEnumerable<T> list in superlist)
        {
            if (index < list.Count())
            {
                yield return list.ElementAt(index);
            }

            index++;
        }
    }
}

Finally you can call Enumerable.Distinct() over the result to get unique values.

Upvotes: 0

King King
King King

Reputation: 63377

This works if the lists contains different items and there are not duplicated Items in a list.

var result = list.SelectMany(x=>x)
                 .OrderBy(x=>list.First(a=>a.Contains(x))
                                 .IndexOf(x));

Upvotes: 0

evanmcdonnal
evanmcdonnal

Reputation: 48134

Just use MyListOfLists.SelectMany(x => x).OrderByDescending(x => x).ToList()

SelectMany will flatten your lists into one. OrderByDescending operates on that result and will put the results in alphabetical order (which I think you want). Then call ToList to force execution and get a List<string> instead of an IEnumerable<string>

Upvotes: 1

user2711965
user2711965

Reputation: 1825

Use Enumerable.SelectMany to flatten the items in the list and create a new list out of it.

var result = superlist.SelectMany(r=> r).ToList();

Upvotes: 0

Related Questions