Comment utiliser LINQ avec un tableau à 2 dimensions

J’ai un tableau d’octets en 2 dimensions qui ressemble à ceci:

0 0 0 0 1

1 1 1 1 0

0 0 1 1 1

1 0 1 0 1

Chaque valeur du tableau ne peut être que 0 ou 1. L’exemple simplifié ci-dessus montre 4 lignes, chaque ligne contenant 5 colonnes. J’essaie de comprendre comment utiliser LINQ pour renvoyer l’index à la ligne contenant le plus grand nombre de 1, ce qui dans l’exemple ci-dessus devrait renvoyer 1.

Le code non LINQ C # suivant résout le problème:

static int GetMaxIndex(byte[,] TwoDArray) { // This method finds the row with the greatest number of 1s set. // int NumRows = TwoDArray.GetLength(0); int NumCols = TwoDArray.GetLength(1); int RowCount, MaxRowCount = 0, MaxRowIndex = 0; // for (int LoopR = 0; LoopR < NumRows; LoopR++) { RowCount = 0; for (int LoopC = 0; LoopC  MaxRowCount) { MaxRowCount = RowCount; MaxRowIndex = LoopR; } } return MaxRowIndex; } static void Main() { byte[,] Array2D = new byte[4, 5] { { 0, 0, 0, 0, 1 }, { 1, 1, 1, 1, 0 }, { 0, 0, 1, 1, 1 }, { 1, 0, 1, 0, 1 } }; int MaxInd = GetMaxIndex(Array2D); Console.WriteLine("MaxInd = {0}", MaxInd); } 

Donc, mes questions sont:

  1. Comment LINQ peut-il être utilisé pour résoudre ce problème, et utiliser LINQ ici serait-il moins efficace que d’utiliser le code non LINQ ci-dessus?
  2. Est-il possible de résoudre ce problème avec PLINQ? Ou serait-il plus efficace d’utiliser la bibliothèque parallèle de tâches (TPL) directement pour le code ci-dessus et de scinder le nombre de 1 dans chaque ligne en un fil distinct, en supposant que chaque ligne comporte au moins 1 000 colonnes?

Il est difficile de travailler avec des tableaux multidimensionnels avec LINQ, mais voici comment procéder:

 var arr = new [,] { { 0, 0, 0, 0, 1 }, { 1, 1, 1, 1, 0 }, { 0, 0, 1, 1, 1 }, { 1, 0, 1, 0, 1 } }; var data = Enumerable.Range(0, 4) .Select( row => new { index = row, count = Enumerable.Range(0, 5).Select(col => arr[row, col]).Count(x => x == 1) }) .OrderByDescending(x => x.count) .Select(x => x.index) .First(); 

Voici comment je le ferais. C’est la même chose que les autres plus ou moins, mais sans aucun Enumerable.Range (pas qu’il n’y a rien qui cloche avec ceux-là (je les utilise tout le temps) … cela rend simplement le code plus indenté dans ce cas).

Celui-ci comprend également des éléments PLINQ. La TPL (asynchrone / wait) ne conviendrait pas à cela, car elle est liée au calcul et que la TPL convient mieux aux opérations liées aux E / S. Votre code finirait par s’exécuter de manière séquentielle si vous utilisiez async / wait plutôt que PLINQ. Cela est dû au fait que async / wait ne sera pas parallèle tant que le thread ne sera pas libéré (et il pourra démarrer la tâche suivante … qui pourrait ensuite devenir parallèle) et des fonctions purement synchrones (telles que les commandes de processeur) ne seront pas toutes réellement attendues. ..ils vont juste courir tout au long. Fondamentalement, il terminerait la première chose de votre liste avant même de commencer la chose suivante, la rendant exécutée séquentiellement. PLINQ démarre explicitement les tâches parallèles et n’a pas ce problème.

 //arry is your 2d byte array (byte[,] arry) var maxIndex = arry .Cast() //cast the entire array into bytes .AsParallel() //make the transition to PLINQ (remove this to not use it) .Select((b, i) => new // create indexes { value = b, index = i }) .GroupBy(g => g.index / arry.GetLength(1)) // group it by rows .Select((g, i) => new { sum = g.Select(g2 => (int)g2.value).Sum(), //sum each row index = i }) .OrderByDescending(g => g.sum) //max by sum .Select(g => g.index) //grab the index .First(); //this should be the highest index 

En termes d’efficacité, vous obtiendrez probablement de meilleurs résultats avec votre boucle. La question que je poserais est, qui est plus lisible et plus clair?

1) Vous pouvez le faire avec LINQ de cette façon …

 private static int GetMaxIndex(byte[,] TwoDArray) { return Enumerable.Range(0, TwoDArray.GetLength(0)) .Select( x => new { Index = x, Count = Enumerable.Range(0, TwoDArray.GetLength(1)).Count(y => TwoDArray[x, y] == 1) }) .OrderByDescending(x => x.Count) .First() .Index; } 

… vous devriez le tester pour voir si LINQ est plus rapide ou plus lent.

2) Il est possible d’utiliser PLINQ. Il suffit d’utiliser ParallelEnumerable.Range pour le générateur d’index de ligne

 private static int GetMaxIndex2(byte[,] TwoDArray) { return ParallelEnumerable.Range(0, TwoDArray.GetLength(0)) .Select( x => new { Index = x, Count = Enumerable.Range(0, TwoDArray.GetLength(1)).Count(y => TwoDArray[x, y] == 1) }) .OrderByDescending(x => x.Count) .First() .Index; } 
 // This code is extracted from // http://www.codeproject.com/Articles/170662/Using-LINQ-and-Extension-Methods-in-C-to-Sort-Vect private static IEnumerable ConvertToSingleDimension(T[,] source) { T[] arRow; for (int row = 0; row < source.GetLength(0); ++row) { arRow = new T[source.GetLength(1)]; for (int col = 0; col < source.GetLength(1); ++col) arRow[col] = source[row, col]; yield return arRow; } } // Convert byte[,] to anonymous type {int index, IEnumerable} for linq operation var result = (from item in ConvertToSingleDimension(Array2D).Select((i, index) => new {Values = i, Index = index}) orderby item.Values.Sum(i => i) descending, item.Index select item.Index).FirstOrDefault();