2次元配列があり、それをリスト(同じオブジェクト)に変換する必要があります。各要素を取得してリストに追加するfor
またはforeach
ループでそれを実行したくありません。それを行う他の方法はありますか?
まあ、それは余分なコピーを作ることを意味しますが、「ブリット」な種類のコピーを使用するようにできます:(
_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);
_
もちろん、1次元配列に満足している場合は、最後の行を無視してください:)
_Buffer.BlockCopy
_は、検証後に非常に効率的なコピーを使用するためにexpectを使用するネイティブメソッドとして実装されます。 _List<T> constructor
_を受け入れる_IEnumerable<T>
_は、_IList<T>
_と同じように_double[]
_を実装する場合に最適化されています。適切なサイズのバッキング配列を作成し、その配列に自分自身をコピーするように要求します。うまくいけば、それは_Buffer.BlockCopy
_または同様のものも使用します。
3つのアプローチ(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ループとブロックコピーはほぼ同じ時間で処理されます。
for
ループが最速の方法です。
LINQでそれができるかもしれませんが、それは遅くなります。また、自分でループを作成することはありませんが、内部的にはループが残っています。
arr.SelectMany(x=>x).ToList()
のようなことができます。T[,]
_の_IEnumerable<T>
_は2D配列のすべての要素を返すので、_T[,]
_では単にarr.ToList()
を実行できます。IEnumerable
のみを実装し、_IEnumerable<T>
_は実装していないようです。したがって、yetanothercoderが提案するように_Cast<double>
_を挿入する必要があります。それはボクシングのためにそれをさらに遅くします。単純なループよりもコードを速くすることができる唯一のことは、要素の数を計算し、正しい容量でリストを構築することです。そのため、成長する必要はありません。
配列が長方形の場合、サイズを_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]);
}
_
理論的には、プライベートリフレクションと安全でないコードを使用して、生のメモリコピーを少し高速に実行できる可能性があります。しかし、私はそれに対して強く助言します。