在“垂直”空间而不是“水平”空间上运行LINQ查询

时间:2018-08-19 21:58:26

标签: c# arrays linq matrix

我什至不知道该怎么做。
一个例子:

int[][] myArrays = {
    new int[] {1, 2, 3},
    new int[] {4, 5, 3},
    new int[] {1, 2, 3}
};
int[] avgArray = myArrays.//Some LINQ statement to average every Nth element in the second dimension (essentially "flatten" the array)
//avgArray == {2, 3, 3}

到目前为止,我只能想到:

int ndDimLen = myArrays.GetLength(1);
int[] avgArray = new int[ndDimLen];
myArrays.Select(
    arr => arr.Select( (n, i) => avgArray[i] += n )
);
avgArray = avgArray.Select( n => n / ndDimLen ).ToArray();

但这不能达到目的,在锯齿状阵列上并不是一个好主意...

此外,我绝对想避免转置,因为在大型阵列上进行操作时速度很慢!

谢谢您的时间!

3 个答案:

答案 0 :(得分:2)

您可以在[Columns]期间遍历[Row]索引。Length报告它在维度中包含一个[Column],您需要对其平均值取平均值。
(为简便起见,使用术语ColumnRow作为视觉辅助)

一个示例,使用Linq的.Average()计算序列的平均值:

int[][] myArrays = {
    new int[] {1, 2, 3},
    new int[] {4, 5, 3},
    new int[] {1, 2, 3},
};

int column = 2;
double avg = myArrays.Select((arr, i) => myArrays[i][column]).Average();

结果:3

结构更复杂时,我们需要验证当前的[Column]是否包含一个值:

int[][] myArrays = {
    new int[] {1, 2, 3},
    new int[] {3, 4, 5},
    new int[] {3, 4},
    new int[] {1, 2, 3, 4},
    new int[] {1},
    new int[] {4, 5, 3}
};

int column= 2;
double? avg = myArrays.Select((arr, i) => 
              ((arr.Length > column) ? myArrays?[i][column] : null)).Average();


Result Column 1:  { 1, 3, 3, 1, 1, 4 } => 2.1666666...
Result Column 2:  { 2, 4, 4, 2, 5 } => 3.4
Result Column 3:  { 3, 5, 3, 3 } => 3.5
Result Column 4:  { 4 } => 4 

相同的方法,适用于所有[Columns]

var Averages = Enumerable.Range(0, myArrays.Max(Arr => Arr.Length))
                         .Select(col => myArrays
                                 .Select((arr, idx) =>
                                     ((arr.Length > col) ? myArrays?[idx][col] : null))
                                 .Average()).ToList();

Enumerable.Range提供了一些灵活性。
上面的代码从int开始生成一系列0元素,并将其值递增到数组中 Colums 的数量(Max(Arr => Arr.Length)选择数组的包含更多元素)。

因此,您可以使用以下方式对第一Index = 0)中的数字进行平均:

var Averages = Enumerable.Range(0, 1).Select( ... )

或从 1到3(Indexes 1 to 3):

var Averages = Enumerable.Range(1, 3).Select( ... )

答案 1 :(得分:1)

是的,可以,但是不能在这个物体上。

myArrays基本上是一个数组的数组,因此LINQ一次只能看到一行,您不能使其看到列,因为这不是它的工作原理。

您可以做的是首先转换此“表”,即更改列和行的位置。 here已经讨论了如何执行此操作,因此,我将仅介绍给您。

使用知识来转置它,您可以制作将要使用的方法,并在其上使用LINQ,例如:

Transpose(myArray).Select(predicate);

答案 2 :(得分:1)

如果数组的长度不相等,则没有指定所需内容:

int[][] myArrays =
{
    new int[] {1, 2},
    new int[] {4, 5, 3, 7, 5, 3, 4, 5, 1},
    new int[] {1, 2, 3}
};

让我们假设您的数组都具有相同的长度。

如果计划定期使用此功能,请考虑为二维数组编写扩展功能。参见Extension Methods Demystified

public static IEnumerable<int> ToVerticalAverage(this int[][] array2D)
{
    if (array2D ==  null) throw new ArgumentNullException(nameof(array2D);

    // if input empty: return empty
    if (array2D.Any())
    {   // there is at least one row
        // check number of columns:
        int nrOfColumns = array2D.First().Length;
        if (!array2D.All(row => row.Length == nrOfColumns)
        {
           // TODO: decide what to do if arrays have unequal length
        }

        // if here, at least one row, all rows have same number of columns
        for(int columNr = 0; columNr < nrOfColumns; ++columNr)
        {
            yield return array2D.Select(row => row[i]).Average();
        }
    }
    // else: no rows, returns empty sequence
}

用法:

int[][] myInputValues = ...
IEnumerable<int> averageColumnValues = myInputValues.ToVerticalAverage();

如果您有多个需要列值的函数,请编写扩展函数以获取列(=转置矩阵)

public static IEnumerable<IEnumerable<int>> Transpose(this int[][] array2D)
{
     // TODO: check input
     int nrOfColumns = array2D.First().Length;
     for(int columNr = 0; columNr < nrOfColumns; ++columNr)
     {
          yield return array2D.Select(row => row[columnNr];
     }
}
public static IEnumerable<int> ToVerticalAverage(this int[][] array2D)
{
    // TODO: check input
    foreach (IEnumerable<int> column in array2D.Transpose())
    {
         yield return column.Average();
    }