Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interleaved merge with LINQ?

Tags:

c#

linq

I'm currently experimenting a bit with LINQ. Let's say I have two collections of identical length:

var first = new string[] { "1", "2", "3" };
var second = new string[] { "a", "b", "c" };

I would like to merge those two collections into one, but in an interleaved fashion. The resulting sequence should thus be:

"1", "a", "2", "b", "3", "c"

What I've come up with so far is a combination of Zip, an anonymous type and SelectMany:

var result = first.Zip( second, ( f, s ) => new { F = f, S = s } )
                  .SelectMany( fs => new string[] { fs.F, fs.S } );

Does anybody know of an alternate/simpler way to achieve such an interleaved merge with LINQ?

like image 980
TeaWolf Avatar asked Aug 28 '11 22:08

TeaWolf


3 Answers

The example you provided can by made simpler by dispensing with the anonymous type:

   var result = first.Zip(second, (f, s) => new[] { f, s })                       .SelectMany(f => f); 
like image 170
Andrew Shepherd Avatar answered Sep 19 '22 16:09

Andrew Shepherd


Warning: this will skip trailing elements if the enumerations have different lengths. If you'd rather substitute in nulls to pad out the shorter collection, use Andrew Shepherd's answer below.


You could write your own Interleave extension method, like in this example.

internal static IEnumerable<T> InterleaveEnumerationsOfEqualLength<T>(     this IEnumerable<T> first,      IEnumerable<T> second) {     using (IEnumerator<T>         enumerator1 = first.GetEnumerator(),         enumerator2 = second.GetEnumerator())     {         while (enumerator1.MoveNext() && enumerator2.MoveNext())         {             yield return enumerator1.Current;             yield return enumerator2.Current;         }     } } 
like image 31
Douglas Avatar answered Sep 19 '22 16:09

Douglas


The given implementation in the accepted answer has an inconsistency:
The resulting sequence will always contain all elements of the first sequence (because of the outer while loop), but if the second sequence contains more elements, than those elements will not be appended.

From an Interleave method I would expect that the resulting sequence contains

  1. only 'pairs' (length of resulting sequence: min(length_1, length_2) * 2)), or that
  2. the remaining elements of the longer sequence are always appended (length of resulting sequence: length_1 + length_2).

The following implementation follows the second approach.
Note the single | in the or-comparison which avoids short-circuit evaluation.

public static IEnumerable<T> Interleave<T> (
    this IEnumerable<T> first, IEnumerable<T> second)
{
  using (var enumerator1 = first.GetEnumerator())
  using (var enumerator2 = second.GetEnumerator())
  {
    bool firstHasMore;
    bool secondHasMore;

    while ((firstHasMore = enumerator1.MoveNext())
         | (secondHasMore = enumerator2.MoveNext()))
    {
      if (firstHasMore)
        yield return enumerator1.Current;

      if (secondHasMore)
        yield return enumerator2.Current;
    }
  }
}
like image 36
Julian Lettner Avatar answered Sep 20 '22 16:09

Julian Lettner