Interleaved merge with 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" }开发者_如何学Go;
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?
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);
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;
}
}
}
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
- only 'pairs' (length of resulting sequence:
min(length_1, length_2) * 2)
), or that - 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;
}
}
}
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]);
var result = first.SelectMany( ( f, i ) => new List<string> { f, second[ i ] } );
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;
}
}
精彩评论