将二维数组转换为列表(一维)的快速方法

Posted

技术标签:

【中文标题】将二维数组转换为列表(一维)的快速方法【英文标题】:Fast way to convert a two dimensional array to a List ( one dimensional ) 【发布时间】:2011-07-05 04:20:33 【问题描述】:

我有一个二维数组,我需要将它转换为一个列表(同一个对象)。我不想使用forforeach 循环来获取每个元素并将其添加到列表中。还有其他方法吗?

【问题讨论】:

该列表应该包含什么? 你的二维数组是矩形的(T[,])还是锯齿状的(T[][])? 为什么要避免循环? 您想要一个简短的还是快速的解决方案? Yanshof:你能在你接受的答案中提到 cmets 吗?你声称你想要一个“快速”的解决方案(给定标题),但你接受了三个答案中最慢的一个。 【参考方案1】:

好吧,你可以让它使用“blit”类型的副本,尽管这确实意味着制作一个额外的副本:(

double[] tmp = new double[array.GetLength(0) * array.GetLength(1)];    
Buffer.BlockCopy(array, 0, tmp, 0, tmp.Length * sizeof(double));
List<double> list = new List<double>(tmp);

当然,如果您对一维数组感到满意,请忽略最后一行 :)

Buffer.BlockCopy 是作为本机方法实现的,我希望 在验证后使用极其高效的复制。接受IEnumerable&lt;T&gt;List&lt;T&gt; constructor 针对实现IList&lt;T&gt; 的情况进行了优化,就像double[] 一样。它将创建一个大小合适的后备数组,并要求它将自己复制到该数组中。希望这将使用Buffer.BlockCopy 或类似的东西。

以下是三种方法(for 循环、Cast&lt;double&gt;().ToList() 和 Buffer.BlockCopy)的快速基准测试:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

class Program

    static void Main(string[] args)
    
        double[,] source = new double[1000, 1000];
        int iterations = 1000;

        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        
            UsingCast(source);
        
        sw.Stop();
        Console.WriteLine("LINQ: 0", sw.ElapsedMilliseconds);

        GC.Collect();
        GC.WaitForPendingFinalizers();

        sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        
            UsingForLoop(source);
        
        sw.Stop();
        Console.WriteLine("For loop: 0", sw.ElapsedMilliseconds);

        GC.Collect();
        GC.WaitForPendingFinalizers();

        sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        
            UsingBlockCopy(source);
        
        sw.Stop();
        Console.WriteLine("Block copy: 0", sw.ElapsedMilliseconds);
    


    static List<double> UsingCast(double[,] array)
    
        return array.Cast<double>().ToList();
    

    static List<double> UsingForLoop(double[,] array)
    
        int width = array.GetLength(0);
        int height = array.GetLength(1);
        List<double> ret = new List<double>(width * height);
        for (int i = 0; i < width; i++)
        
            for (int j = 0; j < height; j++)
            
                ret.Add(array[i, j]);
            
        
        return ret;
    

    static List<double> UsingBlockCopy(double[,] array)
    
        double[] tmp = new double[array.GetLength(0) * array.GetLength(1)];    
        Buffer.BlockCopy(array, 0, tmp, 0, tmp.Length * sizeof(double));
        List<double> list = new List<double>(tmp);
        return list;
    

结果(以毫秒为单位的时间);

LINQ: 253463
For loop: 9563
Block copy: 8697

编辑:将 for 循环更改为在每次迭代时调用 array.GetLength(),for 循环和块复制大约需要相同的时间。

【讨论】:

那个的主要问题是它可以在大对象堆上留下一个大的临时数组。 @CodeInChaos:当然。我们不能告诉List&lt;T&gt; 只使用给定的数组是一种痛苦:( 我认为它仍然可能比循环更快。 告诉List&lt;T&gt; 使用某个数组的问题是我们可以告诉多个列表使用同一个数组。不确定这在实践中有多大的问题。 @CodeInChaos:是的,这就是为什么没有办法做到这一点。这可能是 BCL 团队的正确决定 - 只是对这样的事情感到恼火:) 对循环解决方案的一个有趣观察是,如果交换内循环和外循环,它的速度会慢一倍。如果您按顺序读/写,很可能是因为 CPU 缓存工作得更好。【参考方案2】:

要将double[,] 转换为List&lt;double&gt;,如果您正在寻找单线,这里是

double[,] d = new double[,]

    1.0, 2.0,
    11.0, 22.0,
    111.0, 222.0,
    1111.0, 2222.0,
    11111.0, 22222.0
;
List<double> lst = d.Cast<double>().ToList();


但是,如果您正在寻找高效的东西,我宁愿说您不要使用此代码。 请遵循以下两个答案中的任何一个。两者都在实施更好的技术。

【讨论】:

除此之外,这将最终将数组中的每个double 装箱......它的性能会很差。 @Danny:我不确定这种方法比for 循环更清晰或更容易理解,OP 明确希望避免这种方法。更不用说标题说“快”了。 在我对 1000 x 1000 数组的快速基准测试中,它的执行速度比 for 循环或 Buffer.BlockCopy 解决方案慢 30 倍。考虑到标题的“快速”部分,我很惊讶它被接受了。 @downvoters:感谢您让我知道我知道的比 JonSkeet 还少。 :) 请理解我不会删除答案,因为 OP 在某处使用了此代码并且对此感到满意。告诉我,你们中有多少反对者在企业层面工作?好笑 @naveen:我没有看到任何真正的证据,并且鉴于 OP 可以通过复制和粘贴它们并调整到他的变量名来使用任何答案,它们都同样“快速” “按照这个定义。即使您认为这是最有可能的意图,您的回答也没有表明所涉及的效率低下,这对于为所有读者而不仅仅是原始发帖人提供服务是合适的。【参考方案3】:

for 循环是最快的方法。

您也许可以使用 LINQ 来完成,但速度会慢一些。虽然您自己不编写循环,但在底层仍然有一个循环。

对于锯齿状数组,您可能可以执行arr.SelectMany(x=&gt;x).ToList() 之类的操作。 T[,] 上,您可以简单地执行arr.ToList(),因为T[,]IEnumerable&lt;T&gt; 返回二维数组中的所有元素。 看起来二维数组只实现了IEnumerable 而不是IEnumerable&lt;T&gt;所以你需要插入一个Cast&lt;double&gt;,就像yeanothercoder建议的那样。由于拳击,这会使它变得更慢。

唯一能让代码比天真的循环更快的是计算元素的数量并构造具有正确容量的 List,因此它不需要增长。 如果您的数组是矩形的,您可以获得width*height 的大小,使用锯齿状数组可能会更难。

int width=1000;
int height=3000;
double[,] arr=new double[width,height];
List<double> list=new List<double>(width*height);
int size1=arr.GetLength(1);
int size0=arr.GetLength(0);
for(int i=0;i<size0;i++)
  
  for(int j=0;j<size1;j++)
    list.Add(arr[i,j]);

理论上,可以使用私有反射和不安全代码来加快执行原始内存复制的速度。但我强烈建议不要这样做。

【讨论】:

您能否提供一个示例,说明您在 for 循环中的想法,以便我可以对照我的 Buffer.BlockCopy 方法对其进行基准测试?我期望我的会更快,但我想确保我正在测试正确的东西...... 我认为应该是 arr.Cast().ToList()? 看起来像你的,如果快两倍 @Jon @Danny 这就是我在你写评论时编辑它的原因。 @CodeInChaos:您可以通过不在每次迭代中调用GetLength 来稍微优化您的...但在我的测试中,Buffer.BlockCopy 仍然要快一些。

以上是关于将二维数组转换为列表(一维)的快速方法的主要内容,如果未能解决你的问题,请参考以下文章

将一维数组的索引转换为二维数组 i。 e.行列

python将一维概率数组转换为二维数组

java中二维数组和ArrayList的相互转换

将二维数组转换为一维数组,交替其值

一个元素的二维数组列表进入java中的一维数组列表

将二维 JavaScript 数组转换为一维数组 [重复]