我正在寻找的是一个基本操作(我肯定有一个名字,我只是不知道atm)。我有一个矩阵,如:
{1,2,3}
{A,N,F}
{7,8,9}
我想改变成
{1,A,7}
{2,N,8}
{3,F,9}
(以上只是对象的标识符而不是实际值。实际对象属于同一类型且无序)
我更喜欢它的声明性解决方案,但速度是一个因素。我将不得不翻转几张桌子(每分钟100k格),慢速版本将在关键路径上。
然而,我对可读解决方案仍然更感兴趣。 我正在寻找下面的替代解决方案。 (换句话说,我不是指变化,而是一种不同的方法)
var arrays = rows.Select(row => row.ToArray());
var cellCount = arrays.First().Length;
for(var i = 0;i<cellCount;i++){
yield return GetRow(i,arrays);
}
IEnumerable<T> GetRow(int i,IEnumerable<T[]> rows){
foreach(var row in rows}{
yield return row[i];
}
}
在两个几乎同样可读的解决方案中,我会更快,但可读性在速度之前
修改 它总是一个方阵
答案 0 :(得分:10)
我对这个实现有点不确定。它具有迭代器本地的副作用,但对我来说看起来很干净。这假定每个序列长度相同但应该适用于任何序列。您可以将其视为可变长度Zip()
方法。它应该比其他答案中找到的其他链接LINQ解决方案表现更好,因为它只使用工作所需的最少操作。如果不使用LINQ,可能会更好。甚至可能被认为是最佳的。
public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> source)
{
if (source == null) throw new ArgumentNullException("source");
var enumerators = source.Select(x => x.GetEnumerator()).ToArray();
try
{
while (enumerators.All(x => x.MoveNext()))
{
yield return enumerators.Select(x => x.Current).ToArray();
}
}
finally
{
foreach (var enumerator in enumerators)
enumerator.Dispose();
}
}
答案 1 :(得分:5)
快速谷歌搜索显示了这些解决方案:
答案 2 :(得分:4)
看看这个扩展方法。 Linq transpose。 我不确定性能,但代码看起来很优雅。
答案 3 :(得分:1)
您的问题似乎暗示您要修改原始矩阵。
如果是这种情况,并且如果您能够将矩阵存储为IList<IList<T>> matrix
,那么这只会在方形矩阵的情况下起作用。
for(int i = 0; i < matrix.Count; ++i)
{
for(int j = 0; j < i; ++j)
{
T temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp
}
}
答案 4 :(得分:0)
嗯,你在这里寻找的是一个转变T[][] -> T[][]
。有很多IEnumerabe<IEnumerable<T>>.Transpose()
解决方案,但它们都归结为使用临时查找/键循环枚举,并且它们在大容量性能方面留下了很多不足之处。你的例子实际上工作得更快(虽然你也可以松开第二个foreach)。
首先询问“我是否需要LINQ”。你还没有描述转置矩阵的目的是什么,如果速度确实是你关注的话,你可以做好远离LINQ / foreach并以旧式方式(对于内部)来做
答案 5 :(得分:0)
如果有人有兴趣,这是我的。它以与Jeff相同的方式执行,但似乎稍微快一些(假设那些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;
}
这样,每个元素在返回之前都不会被评估。
答案 6 :(得分:0)
这是我基于枚举器的解决方案,它尝试使用 for
来提高 foreach
/ LINQ 的速度:
public static IEnumerable<IEnumerable<T>> Pivot<T>(this IEnumerable<IEnumerable<T>> src) {
var enums = src.Select(ie => ie.GetEnumerator()).ToList();
var initialMoveNext = Enumerable.Repeat(true, enums.Count).ToList();
for (; ; ) {
var moveNext = initialMoveNext.ToArray(); // initialize to all true
var hasMore = false;
for (int j1 = 0; j1 < enums.Count; ++j1)
if (moveNext[j1]) {
moveNext[j1] = enums[j1].MoveNext();
hasMore = hasMore || moveNext[j1];
}
if (!hasMore)
break;
IEnumerable<T> subEnum() {
for (int j1 = 0; j1 < enums.Count; ++j1) {
if (moveNext[j1])
yield return enums[j1].Current;
}
}
yield return subEnum();
}
for (int j1 = 0; j1 < enums.Count; ++j1)
enums[j1].Dispose();
}