将 c# `Index` 和 `Range` 与锯齿状数组一起使用

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

我正在尝试使用 IndexRange 对象来分割锯齿状数组。但在下面的示例中,我无法提取列,只能提取行。

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

为什么 A[..][0] returns 与 A[0][..] 的结果完全一样?为什么最后一条语句不是每行包含 2 列,而是 3 列?

这里的大局是我正在构建一个 Matrix 对象,它将数值数据存储在 double 的锯齿状数组中,我想使用 IndexRange。我以为我可以用以下方法让它支持切片

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

但这在我的单元测试中失败了。看来即使C#支持语法,结果也是出乎意料的。

有没有一种方法可以使用现有的切片框架实现我想要的功能,而 C# 正在尝试 实现?


抱歉,我来自 Fortran 背景,切片很自然。

此外,我是否是唯一一个发现数组中的最后一个元素被索引为 [^1] 但最后一个元素的范围是 [..^0] 非常令人困惑的人。 ^0Index 的含义与对 Range.

的含义不同,这在 Microsoft 方面似乎非常不一致

列是三个不同的数组。切片运算符不适用于那些不同的数组,只能在一个数组内工作。

您所做的唯一切片是根据运算符对行数组和 returns 列数组进行切片。

A[0][..] returns A 的第一个数组(即第一列)。 [..] 然后 returns 整列。 A[..][0] returns 整个数组(又是 A),然后是 A 中的第一个条目,又是第一列。

这就是我使用锯齿状数组作为扩展方法实现切片和注入的方法

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

}