Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use ranges with List in C#?

Tags:

c#

With C# 8 we got ranges to get "sub lists".

While this works:

var array = new string[] { "abc", "def", "ghi" };
var subArray = array[0..1]; // works

This does not:

var list = new List<string> { "abc", "def", "ghi" };
var subList = list[0..1]; // does not work

How can I use ranges with lists?

like image 631
Sebastian Krysmanski Avatar asked Jun 06 '21 05:06

Sebastian Krysmanski


Video Answer


4 Answers

You can use ToArray() method to solve the problem:

var list = new List<string> { "abc", "def", "ghi" };
var subList = list.ToArray()[0..1]; // works

But note that List<T> has GetRange method that does the same thing:

var list = new List<string> { "abc", "def", "ghi" };
var subList = list.GetRange(0,1);//abc
like image 112
Alireza Ahmadi Avatar answered Oct 17 '22 02:10

Alireza Ahmadi


List does not itself support Ranges. However, if all you have is a Range, you can create a helpful extension method like so:

public static class ListExtensions
{
    public static List<T> GetRange<T>(this List<T> list, Range range)
    {
        var (start, length) = range.GetOffsetAndLength(list.Count);
        return list.GetRange(start, length);
    }
}

Then you can use it to get your sub-List:

var list = new List<string> { "abc", "def", "ghi" };
var subList = list.GetRange(0..1);
like image 28
yaakov Avatar answered Oct 17 '22 02:10

yaakov


Just for completeness, I know this isn't exactly what OP is asking for.

List<T> has a GetRange-Method.

https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.getrange?view=net-5.0

List<?> range = list.GetRange(0,5);

It doesn't have much to do with the new language feature but it does what it says.

Please do not use ToArray only to use that fancy new language feature. It's not worth the overhead.

like image 35
CSharpie Avatar answered Oct 17 '22 02:10

CSharpie


Unfortunately you can't. From the language specification:

For example, the following .NET types support both indices and ranges: String, Span, and ReadOnlySpan. The List supports indices but doesn't support ranges.

Type Support for ranges and indices.

So the only way would be something like this, which is a bit clunky:

var subList = list.ToArray()[0..1];

Edit: I will leave the rest of my answer here for the record, but I think the answer using Range is more elegant, and will be significantly faster, especially for large slices.

Benchmarking and Extension Method

The following demonstrates how much slower using ToArray() can be (using LINQPad - F.Rnd.Str is my own helper method for generating random strings):

var list = new List<string>();
for (int i = 0; i < 100000; i++)
{
    list.Add(F.Rnd.Str);
}

// Using ToArray()
var timer = new Stopwatch();
timer.Start();
var subList0 = list.ToArray()[42..142];
timer.Stop();
timer.ElapsedTicks.Dump("ToArray()");

// Using Indices
timer.Reset();
timer.Start();
var subList1 = new List<string>();
for (int i = 42; i < 142; i++)
{
    subList1.Add(list[i]);
}
timer.Stop();
timer.ElapsedTicks.Dump("Index");

// ToArray()
// 3136
// Index
// 28

Therefore an extension method like this will be very quick:

public static class ListExtensions
{
    public static List<T> GetSlice<T>(this List<T> @this, int first, int last)
    {
        var slice = new List<T>();
        for (int i = first; i < last; i++)
        {
            slice.Add(@this[i]);
        }

        return slice;
    }
}

Then you can add this to the benchmark:

// Using Extension Method
timer.Reset();
timer.Start();
var subList2 = list.GetSlice(42, 142);
timer.Stop();
timer.ElapsedTicks.Dump("Extension");

Benchmark results:

// ToArray()
// 2021
// Index
// 16
// Extension
// 15

Range

One of the answers uses Range to achieve the same thing, so for comparison:

timer.Reset();
timer.Start();
var range = new Range(42, 142);
var (start, length) = range.GetOffsetAndLength(list.Count);
var subList3 = list.GetRange(start, length);
timer.Stop();
timer.ElapsedTicks.Dump("Range");

Benchmark results:

// ToArray()
// 2056
// Index
// 21
// Extension
// 16
// Range
// 17

You can see the bottom three methods are essentially the same in terms of speed (as I run it multiple times they all vary between 15-21 ticks).

like image 24
bfren Avatar answered Oct 17 '22 03:10

bfren