C# 使用 Index 和 Range 简化集合操作

Intro

有的语言数组的索引值是支持负数的,表示从后向前索引,比如:arr[-1]

从 C# 8 开始,C# 支持了数组的反向 Index,和 Range 操作,反向 Index 类似于其他语言中的负索引值,但其实是由编译器帮我们做了一个转换,Range 使得我们对数组截取某一部分的操作会非常简单,下面来看一下如何使用吧

Sample

使用 ^ 可以从集合的最后开始索引元素,如果从数组的最后开始索引元素,最后一个元素应该是 1 而不是0如: arr[^1]

使用 .. 可以基于某个数组截取集合中的某一段创建一个新的数组,比如 var newArray = array[1..^1],再来看一下下面的示例吧

int[] someArray = new int[5] { 1, 2, 3, 4, 5 };
int lastElement = someArray[^1]; // lastElement = 5
lastElement.Dump(); someArray[3..5].Dump(); someArray[1..^1].Dump(); someArray[1..].Dump(); someArray[..^1].Dump(); someArray[..2].Dump();

输出结果如下:

Index

那么它是如何实现的呢,索引值引入了一个新的数据结构 System.Index,当你使用 ^ 运算符的时候,实际转换成了 Index

Index:

public readonly struct Index : IEquatable<Index>
{
public Index(int value, bool fromEnd = false); /// <summary>Create an Index pointing at first element.</summary>
public static Index Start => new Index(0); /// <summary>Create an Index pointing at beyond last element.</summary>
public static Index End => new Index(~0);
//
// Summary:
// Gets a value that indicates whether the index is from the start or the end.
//
// Returns:
// true if the Index is from the end; otherwise, false.
public bool IsFromEnd { get; }
//
// Summary:
// Gets the index value.
//
// Returns:
// The index value.
public int Value { get; } //
// Summary:
// Creates an System.Index from the end of a collection at a specified index position.
//
// Parameters:
// value:
// The index value from the end of a collection.
//
// Returns:
// The Index value.
public static Index FromEnd(int value);
//
// Summary:
// Create an System.Index from the specified index at the start of a collection.
//
// Parameters:
// value:
// The index position from the start of a collection.
//
// Returns:
// The Index value.
public static Index FromStart(int value);
//
// Summary:
// Returns a value that indicates whether the current object is equal to another
// System.Index object.
//
// Parameters:
// other:
// The object to compare with this instance.
//
// Returns:
// true if the current Index object is equal to other; false otherwise.
public bool Equals(Index other);
//
// Summary:
// Calculates the offset from the start of the collection using the given collection length.
//
// Parameters:
// length:
// The length of the collection that the Index will be used with. Must be a positive value.
//
// Returns:
// The offset.
public int GetOffset(int length); //
// Summary:
// Converts integer number to an Index.
//
// Parameters:
// value:
// The integer to convert.
//
// Returns:
// An Index representing the integer.
public static implicit operator Index(int value);
}

如果想要自己自定义的集合支持 Index 这种从数组最后索引的特性,只需要加一个类型是 Index 的索引器就可以了,正向索引也是支持的,int 会自动隐式转换为 Index,除了显示的增加 Index 索引器之外,还可以隐式支持,实现一个 int Count {get;} 的属性(属性名叫 Length 也可以),在实现一个 int 类型的索引器就可以了

写一个简单的小示例:

private class TestCollection
{
public IList<int> Data { get; init; } public int Count => Data.Count;
public int this[int index] => Data[index]; //public int this[Index index] => Data[index.GetOffset(Data.Count)];
}
var array = new TestCollection()
{
Data = new[] { 1, 2, 3 }
};
Console.WriteLine(array[^1]);
Console.WriteLine(array[1]);

Range

Range 是在 Index 的基础上实现的,Range 需要两个 Index 来指定开始和结束

public readonly struct Range : IEquatable<Range>
{
/// <summary>Represent the inclusive start index of the Range.</summary>
public Index Start { get; } /// <summary>Represent the exclusive end index of the Range.</summary>
public Index End { get; } /// <summary>Construct a Range object using the start and end indexes.</summary>
/// <param name="start">Represent the inclusive start index of the range.</param>
/// <param name="end">Represent the exclusive end index of the range.</param>
public Range(Index start, Index end)
{
Start = start;
End = end;
} /// <summary>Create a Range object starting from start index to the end of the collection.</summary>
public static Range StartAt(Index start) => new Range(start, Index.End); /// <summary>Create a Range object starting from first element in the collection to the end Index.</summary>
public static Range EndAt(Index end) => new Range(Index.Start, end); /// <summary>Create a Range object starting from first element to the end.</summary>
public static Range All => new Range(Index.Start, Index.End); /// <summary>Calculate the start offset and length of range object using a collection length.</summary>
/// <param name="length">The length of the collection that the range will be used with. length has to be a positive value.</param>
/// <remarks>
/// For performance reason, we don't validate the input length parameter against negative values.
/// It is expected Range will be used with collections which always have non negative length/count.
/// We validate the range is inside the length scope though.
/// </remarks>
public (int Offset, int Length) GetOffsetAndLength(int length);
}

如何在自己的类中支持 Range 呢?

一种方式是自己直接实现一个类型是 Range 的索引器

另外一种方式是隐式实现,在自定义类中添加一个 Count 属性,然后实现一个 Slice 方法,Slice 方法有两个 int 类型的参数,第一个参数表示 offset,第二个参数表示 length

来看下面这个示例吧,还是刚才那个类,我们支持一下 Range

private class TestCollection
{
public IList<int> Data { get; init; }
//public int[] this[Range range]
//{
// get
// {
// var rangeInfo = range.GetOffsetAndLength(Data.Count);
// return Data.Skip(rangeInfo.Offset).Take(rangeInfo.Length).ToArray();
// }
//} public int Count => Data.Count; public int[] Slice(int start, int length)
{
var array = new int[length];
for (var i = start; i < length && i < Data.Count; i++)
{
array[i] = Data[i];
}
return array;
}
}

More

新的操作符 (^ and ..) 都只是语法糖,本质上是调用 IndexRange

Index 并不是支持负数索引,从最后向前索引只是编译器帮我们做了一个转换,转换成从前到后的索引值,借助于它,我们很多取集合最后一个元素的写法就可以大大的简化了,就可以从原来的 array[array.Length-1] => array[^1]

Range 在创建某个数组的子序列的时候就非常的方便, newArray = array[1..3],需要注意的是,Range 是"左闭右开"的,包含左边界的值,不包含右边界的值

还没使用过 Index/Range 的快去体验一下吧,用它们优化数组的操作吧~~

References

C# 使用 Index 和 Range 简化集合操作的更多相关文章

  1. 使用jdk8 stream简化集合操作

    使用stream的前提是对lambda表达式和函数式接口有一定的了解,同时对方法引用和普通传参的区别有一定的认识. stream的三大特性:1.不存储数据2.不改变源数据3.延时执行. stream优 ...

  2. 函数式Android编程(II):Kotlin语言的集合操作

    原文标题:Functional Android (II): Collection operations in Kotlin 原文链接:http://antonioleiva.com/collectio ...

  3. Linq/List/Array/IEnumerable等集合操作

    来源:http://www.cnblogs.com/liushanshan/archive/2011/01/05/1926263.html 目录 1    LINQ查询结果集    1 2    Sy ...

  4. lodash用法系列(1),数组集合操作

    Lodash用来操作对象和集合,比Underscore拥有更多的功能和更好的性能. 官网:https://lodash.com/引用:<script src="//cdnjs.clou ...

  5. Groovy系列(4)- Groovy集合操作

    Groovy集合操作 Lists List 字面值 您可以按如下所示创建列表. 请注意,[]是空列表表达式 def list = [5, 6, 7, 8] assert list.get(2) == ...

  6. Django数据库性能优化之 - 使用Python集合操作

    前言 最近有个新需求: 人员基础信息(记作人员A),10w 某种类型的人员信息(记作人员B),1000 要求在后台上(Django Admin)分别展示:已录入A的人员B列表.未录入的人员B列表 团队 ...

  7. JAVASE02-Unit04: 集合框架 、 集合操作 —— 线性表

    Unit04: 集合框架 . 集合操作 -- 线性表 操作集合元素相关方法 package day04; import java.util.ArrayList; import java.util.Co ...

  8. Scala集合操作

    大数据技术是数据的集合以及对数据集合的操作技术的统称,具体来说: 1.数据集合:会涉及数据的搜集.存储等,搜集会有很多技术,存储技术现在比较经典方案是使用Hadoop,不过也很多方案采用Kafka.  ...

  9. Python 集合操作

    1.集合操作 集合是一个无序的,不重复的数据组合, 他的主要作业如下. 1.去重,把一个列表变成集合,就自动去重了 2.关系测试,测试两组数据之前的交集.差集.并集等关系 list_1 = [1,4, ...

随机推荐

  1. 小程序navigateTo和redirectTo跳转的区别与应用

    最近在做小程序的跳转,发现navigateTo的跳转无法满足业务需求,所以特地记录下 业务需求 类似一个淘宝的在订单界面选择地址的功能,从A页面点击跳转到B页面的地址列表页面,B页面可以选择已有的地址 ...

  2. SpringBoot - 实现文件上传2(多文件上传、常用上传参数配置)

    在前文中我介绍了 Spring Boot 项目如何实现单文件上传,而多文件上传逻辑和单文件上传基本一致,下面通过样例进行演示. 多文件上传 1,代码编写 1)首先在 static 目录中创建一个 up ...

  3. 2019牛客暑期多校训练营(第四场)A-meeting(树的直径)

    >传送门< 题意:n给城市有n-1条路相连,每两个城市之间的道路花费为1,有k个人在k个城市,问这k个人聚集在同一个城市的最小花费 思路:(官方给的题解写的挺好理解的) 考虑距离最远的两个 ...

  4. 【noi 2.6_1481】Maximum sum(DP)

    题意:求不重叠的2段连续和的最大值. 状态定义f[i]为必选a[i]的最大连续和,mxu[i],mxv[i]分别为前缀和后缀的最大连续和. 注意:初始化f[]为0,而max值为-INF.要看好数据范围 ...

  5. 【noi 2.6_162】Post Office(DP)

    这题和"山区建小学"除了输入不同,其他都一样.(解析可见我的上一篇随笔) 但是,这次我对dis[][]加了一个优化,画一下图就可明白. 1 #include<cstdio&g ...

  6. Java 窗口 小马图像窗口

    写在前面: eclipse接着爽到 全是借鉴的,东改西改,而且记不住原网址了 两个月前写的,忘了思路,嗯,It just works 运行效果: 图像随便选(放到*jar所在目录*\\pictures ...

  7. Atcoder Panasonic Programming Contest 2020

    前三题随便写,D题是一道dfs的水题,但当时没有找到规律,直接卡到结束 A - Kth Term /  Time Limit: 2 sec / Memory Limit: 1024 MB Score ...

  8. Codeforces Round #658 (Div. 2) C1. Prefix Flip (Easy Version) (构造)

    题意:给你两个长度为\(n\)的01串\(s\)和\(t\),可以选择\(s\)的前几位,取反然后反转,保证\(s\)总能通过不超过\(3n\)的操作得到\(t\),输出变换总数,和每次变换的位置. ...

  9. [Golang]-3 函数、多返回值、变参、闭包、递归

    // test01 project main.go package main import ( "fmt" ) // 单返回值的函数 func plus(a int, b int) ...

  10. 网站日志统计以及SOA架构

    网站日志统计相关术语 PV(Page View):页面浏览量或点击量,衡量用户访问的网页数量 UV(Unique Visitor):独立设备的访问次数,根据客户端发送的 Cookie 区分 IP(In ...