Use c# `Index` and `Range` with jagged arrays

Viewed 225

I am trying to use Index and Range objects to slice a jagged array. But in the example below I cannot extract columns, only rows.

var A =  new float[3][];
A[0] = new float[] { 1.1f, 1.2f, 1.3f };
A[1] = new float[] { 2.1f, 2.2f, 2.3f };
A[2] = new float[] { 3.1f, 3.2f, 3.3f };

// This works as intended to extract rows
var A_row1 = A[0][..];      // { 1.1, 1.2, 1.3 } 
var A_row3 = A[^1][..];     // { 3.1, 3.2, 3.3 } 
var A_row23 = A[1..^0][..]; // { { 2.1, 2.2, 2.3}, { 3.1, 3.2, 3.3 } } 

// This does not work as intended to extract columns
var A_col1 = A[..][0];      // { 1.1, 1.2, 1.3 } ** WRONG
var A_col3 = A[..][^1];     // { 3.1, 3.2, 3.3 } ** WRONG
var A_col23 = A[..][1..^0]; // { { 2.1, 2.2, 2.3}, { 3.1, 3.2, 3.3 } } ** WRONG

var A_sub22 = A[0..2][0..2];
// { { 1.1, 1.2, 1.3 }, { 2.1, 2.2, 2.3 } } ** WRONG
// { { 1.1, 1.2 }, { 2.1, 2.2 } } <= WHAT I AM EXPECTING

Why is it that A[..][0] returns the exact same result as A[0][..]? And why doesn't the last statement contain 2 columns per row, but 3?

The bigger picture here is that I am building a Matrix object that stores the numeric data in a jagged array of double and I want to implement slicing using Index and Range. I thought I could have it support slicing with the following methods

public class Matrix 
{
    readonly double[][] Elements;

    ///<summary>Reference a single element</summary>
    public ref double this[Index row, Index column] 
        => ref Elements[row][column];

    ///<summary>Extract a sub-matrix</summary>
    public double[][] this[Range rows, Range columns]
        => Elements[rows][columns];

    ///<summary>Extract a row sub-vector</summary>
    public double[] this[Index row, Range columns]
        => Elements[row][columns];

    ///<summary>Extract a column sub-vector</summary>
    public double[] this[Range rows, Index column] 
        => Elements[rows][column];
}

But this failed spectacularly in my unit testing. It seems that even if C# supports the syntax, the results are unexpected.

Is there a way to implement the functionality I want to have using the existing framework for slicing that C# is trying to implement?


Sorry, I come from a Fortran background where slicing is natural.

Also, am I the only one that finds it very confusing that the last element in an array is indexed as [^1] but the range to the last element is [..^0]. It seems very inconsistent on the part of Microsoft to have ^0 mean different things for Index as it does for Range.

2 Answers

The columns are three different arrays. The slicing operators do not work across those different arrays, only within one array.

The only slicing you are doing is on the row array and returns the column arrays according to the operators.

A[0][..] returns the first array of A (which is the first column). [..] then returns the whole column. A[..][0] returns the whole array (which is A again) and then the first entry in A, which is again the first column.

This is how I implemented slicing and injecting with a jagged array as extension methods

public static class Extensions
{
    public static TData Slice<TData>(this TData[][] matrix, Index row, Index column)
    {
        return matrix[row][column];
    }
    public static TData[] Slice<TData>(this TData[][] matrix, Index row, Range columns)
    {
        var slice = matrix[row];
        return slice[columns];
    }
    public static TData[] Slice<TData>(this TData[][] matrix, Range rows, Index column)
    {
        var slice = matrix[rows];
        var result = new TData[slice.Length];
        for (int i = 0; i < result.Length; i++)
        {
            result[i] = slice[i][column];
        }
        return result;
    }
    public static TData[][] Slice<TData>(this TData[][] matrix, Range rows, Range columns)
    {
        var slice = matrix[rows];
        for (int i = 0; i < slice.Length; i++)
        {
            slice[i] = slice[i][columns];
        }
        return slice;
    }

    public static void Inject<TData>(this TData[][] matrix, Index row, Index column, TData value)
    {
        matrix[row][column] = value;
    }
    public static void Inject<TData>(this TData[][] matrix, Index row, Range columns, TData[] values)
    {
        int n = matrix.Length;
        int m = matrix.Max((row)=>row.Length);
        (int offset, int count) = columns.GetOffsetAndLength(m);
        var slice = matrix[row];
        var data = new ReadOnlySpan<TData>(values);
        var span = new Span<TData>(slice, offset, count);
        data.CopyTo(span);
    }
    public static void Inject<TData>(this TData[][] matrix, Range rows, Index column, TData[] values)
    {
        var slice = matrix[rows];
        for (int i = 0; i < slice.Length; i++)
        {
            slice[i][column] = values[i];
        }
    }
    public static void Inject<TData>(this TData[][] matrix, Range rows, Range columns, TData[][] values)
    {
        int n = matrix.Length;
        int m = matrix.Max((row)=>row.Length);
        (int offset, int count) = columns.GetOffsetAndLength(m);
        var slice = matrix[rows];
        for (int i = 0; i < slice.Length; i++)
        {
            var data = new ReadOnlySpan<TData>(values[i]);
            var span = new Span<TData>(slice[i], offset, count);
            data.CopyTo(span);
        }
    }

}
Related