Подтвердить что ты не робот

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

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

4b9b3361

Ответ 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().
  • В 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]);
}

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