31

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?

Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
TeaWolf
  • 714
  • 5
  • 10

6 Answers6

34

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);
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • Thanks for the simplification, looks like I was doing things the harder way once again. – TeaWolf Aug 29 '11 at 05:31
  • I don't see simpler than this :) – MBen Jan 11 '13 at 20:12
  • 2
    if the first sequence has 2 elements and the second sequence has 1 element then this will produce 2 elements. Be careful with this implementation - it is not a true merge of the 2 lists. – Denis Aug 11 '16 at 17:03
30

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;
        }
    }
}
Community
  • 1
  • 1
Douglas
  • 36,802
  • 9
  • 76
  • 89
  • Certainly a good solution in regards to both reusability and readability. – TeaWolf Aug 29 '11 at 05:27
  • 2
    I think if the first collection is larger, this code would still return them all, however if the second collection is larger, it would skip them. Maybe worth continuing through the second collection after the while loop for consistency :-) – Danny Tuppeny Aug 29 '11 at 09:15
  • @Danny, yes. My preference would be to stop once the shortest runs out, then you don't need to worry about how to fill in the gaps. (Normally I wouldn't put other people's code in my answers, but I'll leave ChaosPandion's edit with Jiri's code in it alone.) – Douglas Aug 29 '11 at 12:39
  • @Douglas - I added the code so we don't need to worry about the removal of the original source. Plus your whole answer relies on their code to begin with. – ChaosPandion Aug 29 '11 at 14:19
  • @Chaos, agreed. I'm sure it wouldn't have gotten so many votes without the code snippet... – Douglas Aug 29 '11 at 22:35
11

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;
    }
  }
}
Julian Lettner
  • 3,309
  • 7
  • 32
  • 49
5

You can just loop and select the array depending on the index:

var result =
  Enumerable.Range(0, first.Length * 2)
  .Select(i => (i % 2 == 0 ? first : second)[i / 2]);
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
2
var result = first.SelectMany( ( f, i ) => new List<string> { f, second[ i ] } );
Kelly Cline
  • 2,206
  • 2
  • 33
  • 57
0

This is a modified version of the answer from @Douglas. This allows for a dynamic number of IEnumerables to interleave, and goes through all elements, not stopping at the end of the shortest IEnumerable.

public static IEnumerable<T> Interleave<T>(params IEnumerable<T>[] enumerables)
        {
            var enumerators = enumerables.Select(e => e.GetEnumerator()).ToList();
            while (enumerators.Any())
            {
                enumerators.RemoveAll(e => {
                    var ended = !e.MoveNext();
                    if (ended) e.Dispose();
                    return ended;
                });

                foreach (var enumerator in enumerators)
                    yield return enumerator.Current;
            }
        }
Leedan Johnson
  • 231
  • 2
  • 7