将一个二维网格用LINQ转换为“菱形” - 可行吗?

12

前几天我需要一个算法将一个二维网格转化为一个菱形(实际上是旋转45度),这样我就能像处理平面可枚举对象那样处理对角线序列,如下所示:

    1 2 3        1         
    4 5 6  =>   4 2      
    7 8 9      7 5 3   
                8 6     
                 9         

我的算法如下:

public static IEnumerable<IEnumerable<T>> RotateGrid<T>(IEnumerable<IEnumerable<T>> grid)
    {
        int bound = grid.Count() - 1;
        int upperLimit = 0;
        int lowerLimit = 0;

        Collection<Collection<T>> rotated = new Collection<Collection<T>>();

        for (int i = 0; i <= (bound * 2); i++)
        {
            Collection<T> row = new Collection<T>();

            for (int j = upperLimit, k = lowerLimit; j >= lowerLimit || k <= upperLimit; j--, k++)
            {
                row.Add(grid.ElementAt(j).ElementAt(k));
            }

            rotated.Add(row);

            if (upperLimit == bound)
                lowerLimit++;

            if (upperLimit < bound)
                upperLimit++;
        }

        return rotated;
    }

使用LINQ可以更加简洁地实现这个吗?

甚至只是更加简洁的格式? :)


你不能将输入作为标准的二维数组吗?输出必须是惰性的吗?我认为使用LINQ不是一个好主意。另外,矩形输入数组在输出中会是什么样子? - Euphoric
我很想看到其他实现同样功能的代码,但是特别想看到一个LINQ实现 - 我知道你可以使用LINQ执行几乎任何转换,但我在这里无法让它正常工作,部分原因是我需要在处理网格时“扩大和缩小”钻石。 - flesh
我认为通过一些数学的参与,LINQ是可能的。 - Euphoric
1
我认为枚举器的构建方式并不是使用LINQ的最佳选择。我认为最好使用(通用的)矩形数组作为输入,并输出(通用的)交错数组。当然,你可以把它做成扩展方法,但我怀疑枚举器并不是这样做的最佳选择。 - Aidiakapi
我同意。特别是为了强制输入必须是矩形/正方形。我无法想象使用不规则数组来完成这个任务。 - Euphoric
显示剩余2条评论
1个回答

5
我来分享我的想法:
void Main()
{
    var lists = new string[] { "123", "456", "789" };

    foreach (var seq in RotateGrid(lists))
        Console.WriteLine(string.Join(", ", seq));
}

public IEnumerable<IEnumerable<T>> RotateGrid<T>(IEnumerable<IEnumerable<T>> grid) 
{
    int rows = grid.Count();
    int cols = grid.First().Count();
    return 
        from i in Enumerable.Range(0, rows + cols - 1)
        select (
            from j in Enumerable.Range(0, i + 1)
            where i - j < rows && j < cols
            select grid.ElementAt(i - j).ElementAt(j)
        );
}

输出:

1
4, 2
7, 5, 3
8, 6
9

这会变得更加简洁和高效,如果你可以假设只使用 IList<T> 而不是仅仅使用 IEnumerable<T>。我正在考虑一种更有效的方法(不使用.ElementAt),这也适用于IEnumerable,如果我成功编写,我将发布它。
更新:以下是我的实际版本,仍然成功地将大量的linq集成在其中。这是一个相当高效的算法,因为它仅为每个 IEnumable 创建了一次枚举器。
public IEnumerable<IEnumerable<T>> RotateGrid<T>(IEnumerable<IEnumerable<T>> grid) 
{
    var enumerators = new LinkedList<IEnumerator<T>>();
    var diagonal = 
        from e in enumerators
        where e.MoveNext()
        select e.Current;
    foreach (var row in grid)
    {
        enumerators.AddFirst(row.GetEnumerator());
        yield return diagonal.ToArray();
    }

    T[] output;
    while (true)
    {
        output = diagonal.ToArray();
        if(output.Length == 0) yield break;
        yield return output;
    }
}

正是我所想的。但它更多地使用了巧妙的数学,而不是LINQ本身。 - Euphoric
我仍然认为这甚至不能被称为LINQ。但无论如何,你得到了你的答案。 - Euphoric
@Euphoric: 我正在开发一个稍微更实际一些的版本,其中包含了一个循环。也许这会让你满意。我永远不会在实际应用中使用它,因为它非常低效。 - recursive
@Recursive Dude 你是我的英雄,虽然我并不需要它,但终于有人理解保持枚举值快速性的美,以及意识到它们是状态类而不仅仅是一点点产生返回值!(+1) 不过对于较大的集合,使用比List<T>更快的集合(如Dictionary<TKey, TValue>)可能是明智的选择,比如在这种情况下使用Dictionary<int, T>,这样你就有一个O(1)的查找。根据这篇文章,如果有超过4个元素,则使用字典查找更快。 - Aidiakapi
@Aidiakapi:我已经将它改为LinkedList<T>。感谢您的建议。我认为这应该可以提高大数据集的性能。 - recursive
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接