“转向”IEnumerable <ienumerable <t>&gt; 90度</ienumerable <t>

时间:2011-02-18 09:31:58

标签: c# linq algorithm declarative

我正在寻找的是一个基本操作(我肯定有一个名字,我只是不知道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]; 
  }
}

在两个几乎同样可读的解决方案中,我会更快,但可读性在速度之前

修改 它总是一个方阵

7 个答案:

答案 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();
}