How to "zip" or "rotate" a variable number of lists?
You can do this by using the Select
extension taking a Func<T, int, TOut>
:
var rotatedList = myList.Select(inner => inner.Select((s, i) => new {s, i}))
.SelectMany(a => a)
.GroupBy(a => a.i, a => a.s)
.Select(a => a.ToList()).ToList();
This will give you another List<List<string>>
.
Breakdown
.Select(inner => inner.Select((s, i) => new {s, i}))
For each inner list, we project the list's content to a new anonymous object with two properties: s
, the string value, and i
the index of that value in the original list.
.SelectMany(a => a)
We flatten the result to a single list
.GroupBy(a => a.i, a => a.s)
We group by the i
property of our anonymous object (recall this is the index) and select the s
property as our values (the string only).
.Select(a => a.ToList()).ToList();
For each groups, we changed the enumerable to a list and another list for all the groups.
You can roll your own ZipMany instance which manually iterates each of the enumerations. This will likely perform better on larger sequences than those using GroupBy
after projecting each sequence:
public static IEnumerable<TResult> ZipMany<TSource, TResult>(
IEnumerable<IEnumerable<TSource>> source,
Func<IEnumerable<TSource>, TResult> selector)
{
// ToList is necessary to avoid deferred execution
var enumerators = source.Select(seq => seq.GetEnumerator()).ToList();
try
{
while (true)
{
foreach (var e in enumerators)
{
bool b = e.MoveNext();
if (!b) yield break;
}
// Again, ToList (or ToArray) is necessary to avoid deferred execution
yield return selector(enumerators.Select(e => e.Current).ToList());
}
}
finally
{
foreach (var e in enumerators)
e.Dispose();
}
}
How about using SelectMany
and GroupBy
with some indexes?
// 1. Project inner lists to a single list (SelectMany)
// 2. Use "GroupBy" to aggregate the item's based on order in the lists
// 3. Strip away any ordering key in the final answer
var query = myList.SelectMany(
xl => xl.Select((vv,ii) => new { Idx = ii, Value = vv }))
.GroupBy(xx => xx.Idx)
.OrderBy(gg => gg.Key)
.Select(gg => gg.Select(xx => xx.Value));
From LinqPad: