Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combine entries from two lists by position using LINQ

Tags:

c#

.net

linq

Say I have two lists with following entries

List<int> a = new List<int> { 1, 2, 5, 10 };
List<int> b = new List<int> { 6, 20, 3 };

I want to create another List c where its entries are items inserted by position from two lists. So List c would contain the following entries:

List<int> c = {1, 6, 2, 20, 5, 3, 10}

Is there a way to do it in .NET using LINQ? I was looking at .Zip() LINQ extension, but wasn't sure how to use it in this case.

Thanks in advance!

like image 659
Eric Avatar asked Dec 02 '22 16:12

Eric


2 Answers

To do it using LINQ, you can use this piece of LINQPad example code:

void Main()
{
    List<int> a = new List<int> { 1, 2, 5, 10 };
    List<int> b = new List<int> { 6, 20, 3 };

    var result = Enumerable.Zip(a, b, (aElement, bElement) => new[] { aElement, bElement })
        .SelectMany(ab => ab)
        .Concat(a.Skip(Math.Min(a.Count, b.Count)))
        .Concat(b.Skip(Math.Min(a.Count, b.Count)));

    result.Dump();
}

Output:

LINQPad example output

This will:

  • Zip the two lists together (which will stop when either runs out of elements)
  • Producing an array containing the two elements (one from a, another from b)
  • Using SelectMany to "flatten" this out to one sequence of values
  • Concatenate in the remainder from either list (only one or neither of the two calls to Concat should add any elements)

Now, having said that, personally I would've used this:

public static IEnumerable<T> Intertwine<T>(this IEnumerable<T> a, IEnumerable<T> b)
{
    using (var enumerator1 = a.GetEnumerator())
    using (var enumerator2 = b.GetEnumerator())
    {
        bool more1 = enumerator1.MoveNext();
        bool more2 = enumerator2.MoveNext();

        while (more1 && more2)
        {
            yield return enumerator1.Current;
            yield return enumerator2.Current;

            more1 = enumerator1.MoveNext();
            more2 = enumerator2.MoveNext();
        }

        while (more1)
        {
            yield return enumerator1.Current;
            more1 = enumerator1.MoveNext();
        }

        while (more2)
        {
            yield return enumerator2.Current;
            more2 = enumerator2.MoveNext();
        }
    }
}

Reasons:

  • It doesn't enumerate a nor b more than once
  • I'm skeptical about the performance of Skip
  • It can work with any IEnumerable<T> and not just List<T>
like image 121
Lasse V. Karlsen Avatar answered Dec 09 '22 14:12

Lasse V. Karlsen


I'd create an extension method to do it.

public static List<T> MergeAll<T>(this List<T> first, List<T> second)
{
    int maxCount = (first.Count > second. Count) ? first.Count : second.Count;
    var ret = new List<T>();
    for (int i = 0; i < maxCount; i++)
    {
        if (first.Count < maxCount)
            ret.Add(first[i]);
        if (second.Count < maxCount)
            ret.Add(second[i]);
    }

    return ret;
}

This would iterate through both lists once. If one list is bigger than the other it will continue to add until it's done.

like image 29
jsmith Avatar answered Dec 09 '22 14:12

jsmith