.net C#中使用Index/Range进行数组切片的运行时复杂度是多少?

l3zydbqr  于 2023-05-02  发布在  .NET
关注(0)|答案(1)|浏览(103)

在C#中,至少有两种不同的方法来“切片”数组。可以使用Linq的Skip()Take()方法(也适用于其他集合类型),如下所示。..
myArr.Skip(10).Take(20);
...或者可以使用新的System.IndexSystem.Range类型:
myArr[10..20]
假设底层集合是可索引/物化的集合,如ArrayList<T>,那么范围索引是否比Skip/Take提供了性能改进?

o7jaxewo

o7jaxewo1#

调用Take<T>(int)将在此实现中结束:

private static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
        {
            return
                source is IPartition<TSource> partition ? partition.Take(count) :
                source is IList<TSource> sourceList ? new ListPartition<TSource>(sourceList, 0, count - 1) :
                new EnumerablePartition<TSource>(source, 0, count - 1);
        }

我们可以更深入地挖掘,但最后很明显,当使用ListPartition<T>作为List或数组时,Take和Skip都是O(1),这是我们所期望的。它需要分配一个临时迭代器示例,但不复制数据。
EnumerablePartition<T>的实现稍微复杂一些,因此在最坏情况下每步的迭代时间为O(n),但它也不会创建数据的副本。
另一方面,如果你这样做

int[] data = new data[30];
int[] result = data[10..20];

这在内部被转换为对T[] RuntimeHelpers.GetSubArray<T>(T[] array, Range range)的调用。已经从签名中可以看出,这创建了数组段的副本。请注意,与Linq操作符不同,范围操作符(无论出于何种原因)仅在数组上受支持,而在IList上不受支持。

相关问题