3

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.

5
  • The operators work as they should. A[..] returns all rows. A[..][0] returns the first item in those rows. A[..] doesn't change the contents of those rows. To get what you wanted you'd have to change how the data is stored and transpose rows into columns Commented Oct 29, 2021 at 11:29
  • Jagged arrays are nested arrays, not 2D arrays. This isn't a C# problem. You'd get the same behavior in every programming language. To get rows with the syntax you want (actually a simpler syntax) you'd have to use a DataFrame in C#, R or Python, eg df["Price"] would return the contents of the Price raw in all three languages Commented Oct 29, 2021 at 11:33
  • The idea of "columns" doesn't really exist in jagged arrays in the first place. You might want to use a 2D array instead (float[,]). If you use that, you might be able to get the columns. Commented Oct 29, 2021 at 11:35
  • am I the only one that finds it very confusing that the last element... yes and no - that syntax follows Python conventions, which is used by all data scientists. The last item is excluded. So [..^1] would exclude the last element. And yes, this is confusing for people that haven't work with Python and Pandas. Changing a common convention would be worse though Commented Oct 29, 2021 at 11:35
  • You can check the .NET DataFrame class here BUT that class is treated almost exclusively as something that only needs to be used by ML.NET. The docs leave a lot to be desired. Commented Oct 29, 2021 at 11:53

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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

}

3 Comments

Note that your this[Range rows, Range columns] destroys your matrix on access, because you modify Elements[rows] directy, without making a copy.
@Evk - that is not true. I did extensive testing and did not notice any unwanted side-effects. I don't see how var slice = Elements[rows] changes the contents of Elements.
Yes you are right, I missed that Elements[rows] of course creates a copy since rows is Range.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.