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

Попробуйте заменить «код» на «pre». Предварительный тег в HTML отмечает текст как предварительно отформатированный, и все строки и пробелы будут отображаться точно так же, как вы их набираете.

23
задан Micha Wiedenmann 17 September 2018 в 14:06
поделиться

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()


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

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

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 и блок-копия занимают примерно одно и то же время.

54
ответ дан Jon Skeet 17 September 2018 в 14:06
поделиться

Петля 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]);
}

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

11
ответ дан CodesInChaos 17 September 2018 в 14:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: