web-dev-qa-db-fra.com

Un moyen rapide de convertir un tableau bidimensionnel en une liste (unidimensionnel)

J'ai un tableau à deux dimensions et je dois le convertir en une liste (même objet). Je ne veux pas le faire avec la boucle for ou foreach qui prendra chaque élément et l'ajoutera à la liste. Y a-t-il une autre façon de procéder?

25
Yanshof

Eh bien, vous pouvez lui faire utiliser une sorte de copie "blit", bien que cela signifie faire une copie supplémentaire :(

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

Si vous êtes satisfait d'un tableau unidimensionnel bien sûr, ignorez simplement la dernière ligne :)

Buffer.BlockCopy Est implémenté comme une méthode native que je attendez pour utiliser une copie extrêmement efficace après validation. Le List<T> constructor Qui accepte un IEnumerable<T> Est optimisé pour le cas où il implémente IList<T>, Comme le fait double[]. Il créera un tableau de support de la bonne taille et lui demandera de se copier dans ce tableau. J'espère que cela utilisera Buffer.BlockCopy Ou quelque chose de similaire aussi.

Voici un test rapide des trois approches (pour boucle, Cast<double>().ToList() et 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;
    }
}

Résultats (temps en millisecondes);

LINQ: 253463
For loop: 9563
Block copy: 8697

EDIT: Après avoir changé la boucle for pour appeler array.GetLength() à chaque itération, la boucle for et la copie de bloc prennent environ le même temps.

57
Jon Skeet

Une boucle for est le moyen le plus rapide.

Vous pourrez peut-être le faire avec LINQ, mais ce sera plus lent. Et même si vous n'écrivez pas de boucle vous-même, sous le capot il y a toujours une boucle.

  • Pour un tableau dentelé, vous pouvez probablement faire quelque chose comme arr.SelectMany(x=>x).ToList().
  • Sur T[,], Vous pouvez simplement faire arr.ToList() puisque le IEnumerable<T> De T[,] Renvoie tous les éléments du tableau 2D. Il semble que le tableau 2D n'implémente que IEnumerable mais pas IEnumerable<T>, Vous devez donc insérer un Cast<double> Comme encore un autre codeur suggéré. Cela le rendra encore plus lent en raison de la boxe.

La seule chose qui peut rendre le code plus rapide que la boucle naïve est de calculer le nombre d'éléments et de construire la liste avec la capacité correcte, donc elle n'a pas besoin de croître.
Si votre tableau est rectangulaire, vous pouvez obtenir la taille comme width*height, Avec des tableaux dentelés, cela peut être plus difficile.

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]);
}

En théorie, il pourrait être possible d'utiliser une réflexion privée et du code non sécurisé pour accélérer un peu la copie de mémoire brute. Mais je déconseille fortement cela.

11
CodesInChaos