It's a bit hard keeping up with your changing specs. Originally, it was a pair of string arrays. I changed that to be a pair of arrays of T
in my answer.
Then you wrote in a comment that "oh, no, I meant N sequences". Finally, after reading that, I noticed that you'd updated your question to ask about N collections expressed as IEnumerable<T>
.
In the mean time, I pointed out that my original answer would work well for N arrays with minimal change. So, here goes:
For N Arrays
I use the params
keyword to remove the need for your combined variable. The params
keyword will compose some or all of the parameters of a method into an array.
Here's a method that can take N arrays:
public static IEnumerable<T> KnitArrays<T>(params T[][] arrays)
{
var maxLen = (from array in arrays select array.Length).Max();
for (var i = 0; i < maxLen; i++)
{
foreach( var array in arrays)
{
yield return array.Length > i ? array[i] : default(T);
}
}
}
It's pretty much the same logic as the original answer. The test code looks the same as well:
var one = new[] { "A1", "B1", "C1" };
var two = new[] { "A2", "B2", "C2", "D2" };
var three = new[] { "A3", "B3" };
var knittedArray = KnitArrays(one, two, three);
List<string> result = knittedArray.ToList();
WriteCollectionContents(result);
Where WriteCollectionContents
spits out the contents of the collection. In this case:
"A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", null, null, "D2", null,
For N Lists and/or Arrays
It turns out that the same basic code can work with IList<T>
, i.e., for both List<T>
and T[]
:
public static IEnumerable<T> KnitILists<T>(params IList<T>[] ilists)
{
var maxLen = (from ilist in ilists select ilist.Count).Max();
for (var i = 0; i < maxLen; i++)
{
foreach (var ilist in ilists)
{
yield return ilist.Count > i ? ilist[i] : default(T);
}
}
}
The test code for this also looks pretty similar - though note the mix or arrays and lists:
var one = new[] { "A1", "B1", "C1" };
var two = new[] { "A2", "B2", "C2", "D2" };
var list3 = new List<string> { "A3", "B3" };
var knittedLists = KnitILists(one, two, list3);
result = knittedLists.ToList();
WriteCollectionContents(result);
With exactly the same result:
"A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", null, null, "D2", null,
The reason it works with IList<T>
is that that interface has a Count
property and an Item
indexer. If you go to ICollection<T>
the Count
property stays, but you lose the Item
indexer.
Once you get to IEnumerable<T>
, both the Count
property and the Item
indexer are gone. The only thing you can do with an IEnumerable
is to iterate through it. As a result, the logic needs to be very different.
I might get around to coming up with a solution. However, it will likely look very similar to @gertarnold's answer.
I'm looking foreword to your upcoming comment about how you really meant for this to work with multi-dimensional arrays as well.
Original answer follows
How about something like this:
public static IEnumerable<T> KnitArrays<T>(T[] first, T[] second)
{
var maxLen = Math.Max(first.Length, second.Length);
for (var i = 0; i < maxLen; i++)
{
yield return first.Length > i ? first[i] : default(T);
yield return second.Length > i ? second[i] : default(T);
}
}
Testing this with:
var one = new[] { "A", "B", "C" };
var two = new[] { "A", "B", "C", "D" };
var knittedArray = KnitArrays(one, two);
List<string> result = knittedArray.ToList();
yields a list that looks like what you are asking. Note that I just return a non-materialized IEnumerable since you were asking about LINQ.