C#中如何用Index与Range实现交错数组及Matrix类的切片功能
交错数组链式切片不符合预期的根本原因
C#中的交错数组是「数组的数组」,本质是一维数组,每一个元素又是另一个一维数组,不存在原生的多维切片语义。链式索引的求值逻辑是从左到右依次执行,而非把两个索引/范围合并为多维切片操作:
- 执行
A[..][0]时,第一步A[..]会先对第一层的行数组做全量切片,返回和原数组完全一致的3行float[][],第二步再对这个返回的行数组取第0个元素,也就是原数组的第0行,结果自然和A[0][..]完全一致。 - 执行
A[0..2][0..2]时,第一步A[0..2]先切取前2行的数组,第二步0..2是对这个包含2个元素的行数组做切片,返回的还是这2行,每一行本身的元素没有被切片,所以每行仍然保留3个元素。
你原来编写的Matrix索引器错误就是直接沿用了交错数组的链式切片逻辑,没有处理列维度的切片操作,因此结果不符合预期。
自定义Matrix类切片实现方案
你可以直接复用C#的Index、Range语法能力,自行在索引器中实现多维度切片逻辑,修改后的索引器代码如下:
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] { get { var selectedRows = Elements[rows]; var result = new double[selectedRows.Length][]; for (int i = 0; i < selectedRows.Length; i++) { result[i] = selectedRows[i][columns]; } return result; } } /// <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] { get { var selectedRows = Elements[rows]; var result = new double[selectedRows.Length]; for (int i = 0; i < selectedRows.Length; i++) { result[i] = selectedRows[i][column]; } return result; } } }
修改后即可实现你预期的Fortran风格切片效果,比如matrix[0..2, 0..2]就会返回前两行前两列的子矩阵,matrix[.., 0]会返回第一列的所有元素。
^0的设计逻辑说明
这个设计不存在语义不一致的问题,和C# Range的左闭右开规则是完全统一的:
- Index类型的
^n语义是「从数组末尾向前数第n个位置的边界」,^1是最后一个元素的前边界,对应最后一个元素;^0是最后一个元素的后边界,对应数组的长度值。 - Range类型遵循左闭右开规则
[起始边界, 结束边界),包含起始边界对应的元素,不包含结束边界对应的元素。如果需要包含最后一个元素,结束边界就需要设为最后一个元素的后边界也就是^0,这样就能把最后一个元素纳入范围。
你觉得容易混淆主要是因为Fortran的切片默认是左闭右闭规则,和C#的设计不同,适应规则后就不会有问题。
内容的提问来源于stack exchange,提问作者John Alexiou
相关产品推荐
相关产品推荐

