简体   繁体   中英

Array operations with n-dimensional array using LINQ (C#)

Assume we have a jagged array

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

To get a sum of second row and sum of second column, it can be written both code lines respectively:

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

But if we have definition of 2-dimensional array

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

the above-cited code will not work due to compiller errors:

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?)

So, the question: How to use LINQ methods with n-dimensional arrays, but not jagged ones? And is where a method to convert rectangular array to jagged?

PS I tried to find the answer in documentation, but without result.

LINQ to Objects is based on the IEnumerable<T> Interface , ie a one-dimensional sequence of values. This means it doesn't mix well with n-dimensional data structures like non-jagged arrays, although it's possible.

You can generate one-dimensional sequence of integers that index into the n-dimensional array:

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

About your question "How to use LINQ methods with n-dimensional arrays":

You can't use most LINQ methods with an dimensional array, because such an array only implements IEnumerable but not IEnumerable<T> and most of the LINQ extension methods are extension methods for IEnumerable<T> .

About the other question: See dtb's answer.

To add to dtb's solution, a more general way of iterating over all items of the array would be:

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

And now:

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

Of course this is ultra-wasteful as we are creating coordinate tuples even for those items that we will end up filtering out with Where , but if you don't know what the selection criteria will be beforehand this is the way to go (or not -- this seems more like an excercise than something you 'd want to do in practice).

I can also imagine how this might be extended for arrays of any rank (not just 2D) using a recursive lambda and something like Tuple , but that crosses over into masochism territory.

The 2D array doesn't have any built in way of iterating over a row or column. It's not too difficult to create your own such method though. See this class for an implementation which gets an enumerable for row and column.

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];
        }
    }
}

You can also flatten the array using a.Cast<int>() but you would then loose all the info about columns/rows

A simpler way is doing like below

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

The simplest LINQ only approach I can see to do these kinds of row and column operations on a two dimensional array is to define the following lookups:

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

Now you can simply do this:

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

As for n-dimensional, it just gets too painful... Sorry.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM