0

I can't figure out how to do this, if even possible.
An example:

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}

To do this so far, I can only think of:

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

But this defeats the purpose, and isn't a particularly good idea on jagged arrays...

Also, I'd definitely like to avoid transposition, as it's quite a slow operation when operating on large arrays!

Thank you for your time!

Patrick
  • 419
  • 4
  • 16
  • 3
    In your example, each sub-array has the same number of elements. Is that going to always be the case? – mjwills Aug 19 '18 at 23:04
  • Your array is an array of int arrays, not a 2D array. So there is no real notion of «columns». You can simulate it but that's not the final purpose of linq to handle this kind of data. – AFract Aug 20 '18 at 05:59

3 Answers3

2

You could iterate throught the [Columns] index while a [Row].Length reports that it contains a [Column] in the dimension whose values you need to average.
(Using the terms Column and Row for simplicity, as a visual aid)

An example, using Linq's .Average() to compute the average value of the sequence:

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

Result: 3

With a more complex structure, we need to verify whether the current [Column] contains a value:

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 

Same method, applied to all the [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 gives some flexibility.
The code above generates a series of int elements starting from 0 and incrementing the value to the number of Colums in the Array (Max(Arr => Arr.Length) selects the Array's Row containing the higher number of elements).

So, you could average the numbers in the first Column only (Index = 0) with:

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

or from Columns 1 to 3 (Indexes 1 to 3):

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

Jimi
  • 29,621
  • 8
  • 43
  • 61
1

Yes, it is possible, but not on this object.

Basically, myArrays is an array of arrays, so LINQ only sees one row at a time, you cannot make it to see columns, because it's just not how it works.

What you could do, is to transpose this "table" first, that is change places of columns and rows. How to do it has already been discussed here so I will just refer you to it.

Using knowledge how to transpose it, you can make a method that will do it, and use LINQ on it, like:

Transpose(myArray).Select(predicate);
stasiaks
  • 1,268
  • 2
  • 14
  • 31
1

You didn't specify what you want if the arrays have unequal length:

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

Let's assume your arrays all have the same length.

If you plan to use this functionality regularly, consider writing an extension function for two dimensional arrays. See 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
}

Usage:

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

If you have several functions where you need the values of the columns, write an extension function to fetch the columns (= transpose the matrix)

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();
    }
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116