Вот мой, если кому-то интересно. Он работает таким же образом, как и у Джеффа, но, кажется, немного быстрее (при условии, что эти ToArrays () необходимы). Там нет видимых петель или временных переменных, и он намного более компактен:
public static IEnumerable<IEnumerable<T>> Transpose<T>(
this IEnumerable<IEnumerable<T>> source)
{
return source
.Select(a => a.Select(b => Enumerable.Repeat(b, 1)))
.Aggregate((a, b) => a.Zip(b, Enumerable.Concat));
}
Если он вам нужен и для пустых списков, он становится таким:
public static IEnumerable<IEnumerable<T>> Transpose<T>(
this IEnumerable<IEnumerable<T>> source)
{
return source
.Select(a => a.Select(b => Enumerable.Repeat(b, 1)))
.DefaultIfEmpty(Enumerable.Empty<IEnumerable<T>>())
.Aggregate((a, b) => a.Zip(b, Enumerable.Concat));
}
Я заметил, что спрашивающий написал, что матрица всегда будет квадратной. Эта реализация (и jeffs) будет оценивать всю строку за раз, но если мы знаем, что матрица квадратная, мы можем переписать функцию zip более подходящим способом:
public static IEnumerable<IEnumerable<T>> Transpose<T>(
this IEnumerable<IEnumerable<T>> source)
{
return source
.Select(a => a.Select(b => Enumerable.Repeat(b, 1)))
.DefaultIfEmpty(Enumerable.Empty<IEnumerable<T>>())
.Aggregate(Zip);
}
public static IEnumerable<IEnumerable<T>> Zip<T>(
IEnumerable<IEnumerable<T>> first,
IEnumerable<IEnumerable<T>> second)
{
var firstEnum = first.GetEnumerator();
var secondEnum = second.GetEnumerator();
while (firstEnum.MoveNext())
yield return ZipHelper(firstEnum.Current, secondEnum);
}
private static IEnumerable<T> ZipHelper<T>(
IEnumerable<T> firstEnumValue,
IEnumerator<IEnumerable<T>> secondEnum)
{
foreach (var item in firstEnumValue)
yield return item;
secondEnum.MoveNext();
foreach (var item in secondEnum.Current)
yield return item;
}
Таким образом, каждый элемент не будет оцениваться, пока не будет возвращен.