我有一个二维数组,需要将其转换为列表(同一对象)。我不想使用 for
或 foreach
循环来遍历每个元素并添加到列表中。有没有其他方法可以做到这一点?
我有一个二维数组,需要将其转换为列表(同一对象)。我不想使用 for
或 foreach
循环来遍历每个元素并添加到列表中。有没有其他方法可以做到这一点?
好吧,你可以使用“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<T>
的 List<T> 构造函数
在实现了 IList<T>
的情况下进行了优化,像 double[]
一样。它将创建正确大小的后备数组,并要求它将自身复制到该数组中。希望这也会使用 Buffer.BlockCopy
或类似的东西。
以下是三种方法(for循环,Cast<double>().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 循环和块复制的时间大致相同。
List<T>
只使用给定的数组,这真是一件痛苦的事情:(不过我认为这仍然比循环要快。 - Jon SkeetList<T>
使用特定数组的问题在于,我们可能会告诉多个列表使用同一个数组。不确定这在实践中会成为多大的问题。 - CodesInChaos如果你想要一个一行代码的方式将 double[,]
转化为 List<double>
,可以使用以下方法:
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();
for
循环更清晰或更易理解,而OP明确希望避免使用for
循环。更不用说标题中还有“快速”两个字。 - Cody Grayfor
循环是最快的方法。
你可能可以使用LINQ,但那会更慢。而且尽管你不必亲自编写循环,但在幕后仍然有一个循环。
arr.SelectMany(x=>x).ToList()
。T[,]
,您只需使用arr.ToList()
即可,因为T[,]
的IEnumerable<T>
返回2D数组中的所有元素。IEnumerable
但没有IEnumerable<T>
所以您需要像yetanothercoder建议的那样插入一个Cast<double>
。这将由于装箱而使其变慢。唯一能使代码比朴素的循环更快的方法是计算元素数量并使用正确的容量构造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
方法进行基准测试了吗?我期望我的方法更快,但我想确保我正在测试正确的东西... - Jon Skeet
T[,]
)是矩形的还是交错的(T[][]
)? - CodesInChaos