Операции массива с n-мерным массивом с использованием LINQ (C#)

предположим, что у нас есть зубчатый массив

int[][] a = { new[] { 1, 2, 3, 4 }, new[] { 5, 6, 7, 8 }, new[] { 9, 10, 11, 12 } };

чтобы получить сумму второй строки и сумму второго столбца, можно записать обе строки кода соответственно:

int rowSum = a[1].Sum();
int colSum = a.Select(row => row[1]).Sum();

но если у нас есть определение 2-мерного массива

int[,] a = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };

приведенный выше код не будет работать из-за ошибок компилятора:

Error   1   Wrong number of indices inside []; expected 2
Error   2   'int[*,*]' does not contain a definition for 'Select' and no extension method 'Select' accepting a first argument of type 'int[*,*]' could be found (are you missing a using directive or an assembly reference?)

Итак, вопрос: Как использовать методы LINQ с n-мерными массивами, но не зубчатыми? И где метод преобразования прямоугольной массив неровные?

P. S. Я пытался найти ответ в документации, но без результата.

6 ответов


LINQ to Objects на основе IEnumerable Интерфейс, т. е. одномерная последовательность значений. Это означает, что он плохо сочетается с N-мерными структурами данных, такими как неровные массивы, хотя это возможно.

вы можете генерировать одномерную последовательность целых чисел, которые индексируются в n-мерный массив:

int rowSum = Enumerable.Range(0, a.GetLength(1)).Sum(i => a[1, i]);

int colSum = Enumerable.Range(0, a.GetLength(0)).Sum(i => a[i, 1]);

о вашем вопросе "как использовать методы LINQ с n-мерными массивами":

вы не можете использовать большинство методов LINQ с N-мерным массивом, потому что такой массив реализует только IEnumerable а не IEnumerable<T> и большинство методов расширения LINQ являются методами расширения для IEnumerable<T>.

о другом вопросе: см. ответ dtb.


чтобы добавить к решению dtb, более общий способ итерации по всем элементам массива будет:

int[,] b = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };

var flattenedArray = Enumerable.Range(0, b.GetLength(0))
                     .SelectMany(i => Enumerable.Range(0, b.GetLength(1))
                         .Select(j => new { Row = i, Col = j }));

и так:

var rowSum2 = flattenedArray.Where(t => t.Row == 1).Sum(t => b[t.Row, t.Col]);
var colSum2 = flattenedArray.Where(t => t.Col == 1).Sum(t => b[t.Row, t.Col]);

конечно, это ультра-расточительно, поскольку мы создаем координатные кортежи даже для тех элементов, которые мы в конечном итоге отфильтруем с Where, но если вы не знаете, какие критерии отбора будут заранее, это путь (или нет - это больше похоже на упражнение, чем на то, что вы хотите сделать в практика.)

Я также могу представить, как это может быть расширено для массивов любого ранга (а не только 2D) с использованием рекурсивной лямбды и что-то вроде Tuple, но это переходит на территорию мазохизма.


2D-массив не имеет встроенного способа итерации по строке или столбцу. Однако создать свой собственный такой метод не слишком сложно. См. этот класс для реализации, которая получает enumerable для строки и столбца.

public static class LINQTo2DArray
{
    public static IEnumerable<T> Row<T>(this T[,] Array, int Row)
    {
        for (int i = 0; i < Array.GetLength(1); i++)
        {
            yield return Array[Row, i];
        }
    }
    public static IEnumerable<T> Column<T>(this T[,] Array, int Column)
    {
        for (int i = 0; i < Array.GetLength(0); i++)
        {
            yield return Array[i, Column];
        }
    }
}

вы также можете сгладить массив, используяa.Cast<int>() но тогда вы потеряете всю информацию о столбцы/строки


более простой способ делает, как показано ниже

 var t = new List<Tuple<int, int>>();
 int[][] a = t.Select(x => new int[]{ x.Item1, x.Item2}).ToArray(); 

самый простой подход LINQ only, который я вижу, чтобы выполнять такие операции строк и столбцов в двумерном массиве, - это определить следующие запросы:

var cols = a
    .OfType<int>()
    .Select((x, n) => new { x, n, })
    .ToLookup(xn => xn.n % a.GetLength(1), xn => xn.x);

var rows = a
    .OfType<int>()
    .Select((x, n) => new { x, n, })
    .ToLookup(xn => xn.n / a.GetLength(1), xn => xn.x);

теперь вы можете просто сделать это:

var firstColumnSum = cols[0].Sum();

Что касается n-мерного, это просто становится слишком болезненным... Извиняюсь.