【问题标题】:Operate LINQ queries on 'vertical' rather than 'horizontal' space在“垂直”而不是“水平”空间上操作 LINQ 查询
【发布时间】:2019-01-26 01:49:43
【问题描述】:

如果可能的话,我不知道该怎么做。
一个例子:

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

但这违背了目的,对于锯齿状数组来说并不是一个特别好的主意......

另外,我绝对希望避免转置,因为在大型数组上操作时它是一个相当慢的操作!

感谢您的宝贵时间!

【问题讨论】:

  • 在您的示例中,每个子数组具有相同数量的元素。会一直这样吗?
  • 您的数组是 int 数组的数组,而不是二维数组。所以没有真正的«列»概念。您可以模拟它,但这不是 linq 处理此类数据的最终目的。

标签: c# arrays linq matrix


【解决方案1】:

您可以遍历[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 提供了一些灵活性。
上面的代码从0 开始生成一系列int 元素,并将值递增到数组中 的数量(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( ... )

【讨论】:

    【解决方案2】:

    如果数组长度不等,您没有指定您想要的内容:

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

    【讨论】:

      【解决方案3】:

      是的,有可能,但不是在这个对象上。

      基本上,myArrays 是一个数组数组,所以 LINQ 一次只能看到一行,你不能让它看到列,因为它不是这样工作的。

      你可以做的,是先转置这个“表”,即改变列和行的位置。如何做到这一点已经讨论过here,所以我会推荐给你。

      使用如何转置它的知识,您可以制作一个可以做到这一点的方法,并在其上使用 LINQ,例如:

      Transpose(myArray).Select(predicate);
      

      【讨论】:

        猜你喜欢
        • 2013-12-22
        • 1970-01-01
        • 2017-10-18
        • 2019-08-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多