Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any consideration to support using Range for Slicing? #66

Open
JackTheSpades opened this issue Oct 8, 2024 · 5 comments
Open

Any consideration to support using Range for Slicing? #66

JackTheSpades opened this issue Oct 8, 2024 · 5 comments

Comments

@JackTheSpades
Copy link

Range being the C# notation of start..end to with start being inclusive and end exclusive.
Range/Index also support backwards indexing with the ^ prefix.

I've quickly cobbled together a proof of concept.
It has no way of supporting step width so not quite as flexible as slices / python-notation but the fact that it has actual build in language support and allows the usage of variables more directly, I think, makes up for it.

using NumpyDotNet;

ndarray A = np.arange(0.0, 120.0).reshape([2, 3, 4, 5]);
ndArrayWrapper wA = new(A);

Console.WriteLine(wA[0, 0, 0, 0]);           // first element
Console.WriteLine(wA[^1, ^1, ^1, ^1]);       // last element

Console.WriteLine(wA[.., 0, 0, 0]);          // first slice (2,)
Console.WriteLine(wA[.., 1, .., 1..4]);      // mixed slice (2,4,4)

int a = 1, b = 3;
Console.WriteLine(wA[..a, a..b, .., b..]);     // support variables


// wrapper class for ndarray to have a custom index
class ndArrayWrapper(ndarray A)
{
    public object this[params object[] indices]
    {
        get
        {
            if (A.ndim != indices.Length)
                throw new ArgumentException();

            var dims = A.dims;
            var slices = indices.Select((my, i) =>
            {
                if (my is int it)
                    return (object)it;
                if (my is Index idx)
                    return (object)idx.GetOffset((int)dims[i]);
                else if (my is Range ran)
                    return (object)new Slice(
                        ran.Start.GetOffset((int)dims[i]),
                        ran.End.GetOffset((int)dims[i]),
                        1);
                else
                    throw new Exception();
            }).ToArray();

            return A[slices];
        }
    }
}
@KevinBaselinesw
Copy link
Collaborator

I think I looked at this feature a few years ago. What I recall is that the slices are "local" only. In other words, if you take a slice of something, you can't pass that slice to another function. I may not be remembering it exactly right but there was some limitation like that.

If I did accept this as some sort of additional way to specify slice, how do I pass it to the lower layers of code which need to operate on the sliced view? Does the framework allow me to detect that what the start and end indexes are?

What happens if you try to apply this Range syntax to a ndarray that is already numpy sliced? As you may know, the original array is untouched in a slice view. All we really do is build a data structure that allows numpy code to walk the specified slice values. It seems like lots of chances for the .net range to mess up if the array is also sliced.

@JackTheSpades
Copy link
Author

I don't know about the internal handling, like if it can be further optimized but if you look at my cobbled together example, I just convert the Index and Range parameters into int and the build-in Slice objects.
So really, anything that works in the examples listed in the readme should work with Range and Index. I tried this with the same wrapper class as above and it seems to work just fine:

var all = np.arange(0, 10);
var allWrapper = new ndArrayWrapper(all);
Console.WriteLine(all);

var slice1 = (ndarray)allWrapper[2..^2];
Console.WriteLine(slice1);
var slice1Wrapper = new ndArrayWrapper(slice1);

var slice2 = (ndarray)slice1Wrapper[2..^2];
Console.WriteLine(slice2);

all[5] = -5;
Console.WriteLine(all);
Console.WriteLine(slice1);
Console.WriteLine(slice2);

Which outputs the following:

shape=(10,), INT32
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

shape=(6,), INT32
{ 2, 3, 4, 5, 6, 7 }

shape=(2,), INT32
{ 4, 5 }

shape=(10,), INT32
{ 0, 1, 2, 3, 4, -5, 6, 7, 8, 9 }

shape=(6,), INT32
{ 2, 3, 4, -5, 6, 7 }

shape=(2,), INT32
{ 4, -5 }

Slices are local in the sense that if you apply them to a normal C# array, they will create a copy. See this example:

int[] arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
int[] arr2 = arr[2..^2];

Console.WriteLine(string.Join(",", arr));    // 0,1,2,3,4,5,6,7,8,9
Console.WriteLine(string.Join(",", arr2));   // 2,3,4,5,6,7

arr[5] = -5;

Console.WriteLine(string.Join(",", arr));    // 0,1,2,3,4,-5,6,7,8,9
Console.WriteLine(string.Join(",", arr2));   // 2,3,4,5,6,7

However, Range and Index are just that... indices. They just use the length (see my usage of dims) to calculate the int based index. If you have a custom class that provides a Length or Count property with an this[int index] getter, then C# will automatically support usage of Index and Range... but if you just handle them yourself you can do whatever you want with the information.

@KevinBaselinesw
Copy link
Collaborator

I will take a closer look at this. thanks.

@KevinBaselinesw
Copy link
Collaborator

I can see how this could add some value.

However, I think this language feature requires upgrading the toolset to a new compiler/.net standard version.
I am not sure I am ready to do that as that might break existing users.

@KevinBaselinesw
Copy link
Collaborator

I almost want to include your code as some sort of extension or sample code for those applications using the new versions of .NET that can make use of the range syntax.

I am thinking I could add your code to the install package as a wrapper class and update the documentation to call it out.

Do you have any other suggestions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants