Readable C# equivalent of Python slice operation

46.7k views Asked by At

What is the C# equivalent of Python slice operations?

my_list = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
result1 = my_list[2:4]
result2 = my_list[1:]
result3 = my_list[:3]
result4 = my_list[:3] + my_list[4:]

Some of it is covered here, but it is ugly and doesn't address all the uses of slicing to the point of it not obviously answering the question.

8

There are 8 answers

5
Ben On BEST ANSWER

The closest is really LINQ .Skip() and .Take()

Example:

var result1 = myList.Skip(2).Take(2);
var result2 = myList.Skip(1);
var result3 = myList.Take(3);
var result4 = myList.Take(3).Concat(myList.Skip(4));
0
Timothy C. Quinn On

Here is my implementation that follows the JavaScript's Array.slice method which I believe is similar to Pythons implementation:


public static IEnumerable<T> slice<T>(List<T> e, int? n1 = null, int? n2 = null)
{
    if (e == null)
        throw new Exception($"source must not be null");
    int iFrom;
    int iTo;
    if (n1 == null)
    {
        iFrom = 0;
        iTo = e.Count;
    }
    else if (n2 == null)
    {
        if (n1 < 0)
        {
            iFrom = e.Count + (int)n1;
            if (iFrom < 0)
                iFrom = 0;
            iTo = e.Count;
        }
        else
        {
            iFrom = (int)n1;
            iTo = e.Count;
        }
    }
    else
    {
        if (n1 == null)
            throw new Exception("n1 must be an integer if n2 is specified but got null");
        if (n1 < 0)
            throw new Exception($"n1 must be an integer >= 0 if n2 is specified but got {n1}");
        iFrom = (int)n1;
        iTo = (int)n2;
        if (iTo < 0)
            iTo = e.Count + iTo;
        if (iTo <= iFrom)
            iTo = iFrom;
    }


    return e.GetRange(iFrom, iTo - iFrom);
}

public static string[] slice(string[] source, int? n1 = null, int? n2 = null)
{
    return slice(source.ToList(), n1, n2).ToArray();
}

public static object[] slice(object[] source, int? n1 = null, int? n2 = null)
{
    return slice(source.ToList(), n1, n2).ToArray();
}

public static int[] slice(int[] source, int? n1 = null, int? n2 = null)
{
    return slice(source.ToList(), n1, n2).ToArray();
}

The above will work with List and Arrays (I implemented str, int, obj).

If there is a more elegant way to implement the Array APIs, please suggest. I'm new to C#.

2
Cristian Garcia On

This way you don't have to subtract

public static IEnumerable<A> Slice<A> (int from, int to, IEnumerable<A> e) {
    return e.Take (to).Skip (from);
}
1
Ousmane D. On

As of C#8 slicing becomes a lot easier for indexed data structures.

var result1 = myList[2..5]; // end (5) is exclusive
var result2 = myList[1..^0]; // from index 1 to the end 
var result3 = myList[0..3]; // end (3) exclusive

Read more about Ranges and indices here and here.

0
Jabba On

Write a custom extension:

public static List<T> Slice<T>(this List<T> li, int start, int end)
{
    if (start < 0)    // support negative indexing
    {
        start = li.Count + start;
    }
    if (end < 0)    // support negative indexing
    {
        end = li.Count + end;
    }
    if (start > li.Count)    // if the start value is too high
    {
        start = li.Count;
    }
    if (end > li.Count)    // if the end value is too high
    {
        end = li.Count;
    }
    var count = end - start;             // calculate count (number of elements)
    return li.GetRange(start, count);    // return a shallow copy of li of count elements
}

Some tests:

[Fact]
public void Slice_list()
{
    var li1 = new List<char> {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
    Assert.Equal(new List<char> {'c', 'd'}, li1.Slice(2, 4));
    Assert.Equal(new List<char> {'b', 'c', 'd', 'e', 'f', 'g'}, li1.Slice(1, li1.Count));
    Assert.Equal(new List<char> {'a', 'b', 'c'}, li1.Slice(0, 3));
    Assert.Equal(li1, li1.Slice(0, 4).Concat(li1.Slice(4, li1.Count)));
    Assert.Equal(li1, li1.Slice(0, 100));
    Assert.Equal(new List<char>(), li1.Slice(100, 200));

    Assert.Equal(new List<char> {'g'}, li1.Slice(-1, li1.Count));
    Assert.Equal(new List<char> {'f', 'g'}, li1.Slice(-2, li1.Count));
    Assert.Equal(new List<char> {'a', 'b', 'c', 'd', 'e', 'f'}, li1.Slice(0, -1));

    Assert.Equal(new List<char> {'c', 'd', 'e'}, li1.Slice(2, -2));
}
0
Alex On

Here's an extension:

public static IEnumerable<T> Slice<T>(this IEnumerable<T> source, int start = 0, int end = 0)
{
    start = (start >= 0) ? start : source.Count() + start;
    end = (end > 0) ? end : source.Count() + end;

    return source.Skip(start).Take(end - start);
}

Examples:

var input = new[] { 0, 1, 2, 3, 4, 5, 6, 7 };
numbers.Slice(1, 4);    // { 1, 2, 3 }
numbers.Slice(-3, -1);  // { 5, 6 }
numbers.Slice(5);       // { 5, 6, 7 }
numbers.Slice(end:-4);  // { 0, 1, 2, 3 }
0
bashrc On

If you have a List GetRange can come in handy.

From MSDN link:

A shallow copy of a collection of reference types, or a subset of that collection, contains only the references to the elements of the collection. The objects themselves are not copied. The references in the new list point to the same objects as the references in the original list.

The Slice function can then be:

public static IEnumerable<T> Slice<T>(this List<T> source, int from, int to) => source.GetRange(from, to - from);

Negative ranges that python slice supports can also be handled with some loss of cleanliness.

1
Maurits van Riezen On
public static T[] slice<T>(T[] l, int from, int to)
{
    T[] r = new T[to - from];
    for (int i = from; i < to; i++)
    {
        r[i-from]=l[i];
    }
    return r;
}