Операции массива с 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-мерный массив:
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-мерного, это просто становится слишком болезненным... Извиняюсь.