Быстрый способ преобразования двумерного массива в список (одномерный)

У меня есть двумерный массив, и мне нужно преобразовать его в список (тот же объект). Я не хочу делать это с for или же foreach цикл, который будет принимать каждый элемент и добавлять его в список. Есть ли другой способ сделать это?

3 ответа

Решение

Преобразовать 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()


Но, если вы ищете что-то эффективное, я бы сказал, что вы не используете этот код.
Пожалуйста, следуйте одному из двух ответов, указанных ниже. Оба реализуют гораздо более совершенные методы.

Ну, вы можете сделать так, чтобы он использовал "блицкую" копию, хотя это означает создание дополнительной копии:(

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

РЕДАКТИРОВАТЬ: изменив цикл для вызова array.GetLength() на каждой итерации цикл for и копия блока занимают примерно одно и то же время.

for петля самый быстрый способ.

Вы можете сделать это с помощью LINQ, но это будет медленнее. И хотя вы сами не пишете цикл, под капотом все еще есть цикл.

  • Для зубчатого массива вы можете сделать что-то вроде arr.SelectMany(x=>x).ToList(),
  • На T[,] ты можешь просто сделать arr.ToList() так как IEnumerable<T> из T[,] возвращает все элементы в массиве 2D. Похоже, 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]);
}

Теоретически может быть возможно использовать частное отражение и небезопасный код, чтобы сделать его более быстрым, делая необработанную копию памяти. Но я настоятельно советую против этого.

Другие вопросы по тегам