У меня есть двумерный массив, и мне нужно преобразовать его в список (тот же объект). Я не хочу делать это с циклом for
или foreach
, который будет брать каждый элемент и добавлять его в список. Есть ли другой способ сделать это?
Быстрый способ преобразования двумерного массива в список (одномерный)
Ответ 1
Чтобы преобразовать 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();
Но если вы ищете что-то эффективное, я бы сказал, что вы не используете этот код.
Пожалуйста, следуйте одному из двух ответов, указанных ниже. Оба реализуют намного лучшие методы.
Ответ 2
Ну, вы можете использовать его как "блистающую" копию, хотя это означает создание дополнительной копии: (
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
реализуется как родной метод, который я ожидаю использовать с очень эффективным копированием после проверки. List<T> constructor
, который принимает IEnumerable<T>
, оптимизирован для случая, когда он реализует IList<T>
, как это делает double[]
. Он создаст массив поддержки нужного размера и попросит его скопировать себя в этот массив. Надеюсь, что будет использовать Buffer.BlockCopy
или что-то подобное тоже.
Здесь приведен быстрый тест трех подходов (для цикла, 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 и блок копируются примерно в одно и то же время.
Ответ 3
A for
- самый быстрый способ.
Вы можете сделать это с помощью LINQ, но это будет медленнее. И пока вы не пишете цикл самостоятельно, под капотом еще есть цикл.
- Для зубчатого массива вы можете сделать что-то вроде
arr.SelectMany(x=>x).ToList()
. -
ВПохоже, что 2D-массив реализует толькоT[,]
вы можете просто сделатьarr.ToList()
, так какIEnumerable<T>
изT[,]
возвращает все элементы в 2D-массиве.IEnumerable
, но неIEnumerable<T>
, поэтому вам нужно вставить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]);
}
В теории может быть возможно использовать частное отражение и небезопасный код, чтобы сделать его немного быстрее, делая копию необработанной памяти. Но я категорически против этого.