Skip to content

Commit

Permalink
Support negative base arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
BCSharp committed Dec 2, 2024
1 parent 06676cc commit b6a28dc
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 4 deletions.
12 changes: 8 additions & 4 deletions Src/IronPython/Runtime/Operations/ArrayOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -437,8 +437,12 @@ private static int[] TupleToIndices(Array a, IList<object?> tuple) {

private static int FixIndex(Array a, int v, int axis) {
int idx = v;
if (idx < 0) idx += a.GetUpperBound(axis) + 1;
if (idx < a.GetLowerBound(axis) || idx > a.GetUpperBound(axis)) {
int lb = a.GetLowerBound(axis);
int ub = a.GetUpperBound(axis);
if (idx < 0 && lb >= 0) {
idx += ub + 1;
}
if (idx < lb || idx > ub) {
throw PythonOps.IndexError("index out of range: {0}", v);
}
return idx;
Expand All @@ -464,7 +468,7 @@ private static void FixSlice(Slice slice, Array a, out int ostart, out int ostop
} else {
ostart = Converter.ConvertToIndex(slice.start);
if (ostart < lb) {
if (ostart < 0) {
if (ostart < 0 && lb >= 0) {
ostart += ub + 1;
}
if (ostart < lb) {
Expand All @@ -480,7 +484,7 @@ private static void FixSlice(Slice slice, Array a, out int ostart, out int ostop
} else {
ostop = Converter.ConvertToIndex(slice.stop);
if (ostop < lb) {
if (ostop < 0) {
if (ostop < 0 && lb >= 0) {
ostop += ub + 1;
}
if (ostop < 0) {
Expand Down
63 changes: 63 additions & 0 deletions Tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@
## Test array support by IronPython (System.Array)
##

"""
Indexing of CLI arrays in IronPython:
| Base | Index >= 0 | Index < 0 |
|------|--------------------------------------|-------------------|
| > 0 | absolue | relative from end |
| 0 | absolute == relative from beginning | relative from end |
| < 0 | absolute | absolute |
Comparison to indexing in C# and CPython:
* Index >= 0, any base is C# compliant.
* Base 0, any index is CPython compliant.
* Base 0, index < 0 is not supported by C# but can be achieved by `System.Index` with 1-dim arrays only; then IronPython indexing is C# compliant.
* Base > 0, index < 0 is not supported by C#; IronPython follows CPython convention as more practical.
* Base < 0, index < 0 is C# compliant.
* Base != 0 is not supported by CPython for any builtin structures.
"""

from iptest import IronPythonTestCase, is_cli, run_test, skipUnlessIronPython

if is_cli:
Expand Down Expand Up @@ -146,6 +166,13 @@ def test_slice(self):
def f(): array1[::2] = [x * 2 for x in range(11)]
self.assertRaises(ValueError, f)

# slices on non-1-dim arrays are not supported
array2 = System.Array.CreateInstance(int, 20, 20)
self.assertRaises(NotImplementedError, lambda: array2[:]) # TODO: TypeError?
self.assertRaises(TypeError, lambda: array2[:, :]) # TODO: NotImplementedError? This would work in Numpy and Sympy
self.assertRaises(TypeError, lambda: array2[:, :, :]) # OK


def test_creation(self):
t = System.Array
ti = type(System.Array.CreateInstance(int, 1))
Expand Down Expand Up @@ -239,6 +266,10 @@ def sliceArrayAssign(arr, index, val):
self.assertRaises(NotImplementedError, sliceArrayAssign, a, 1, 1)

def test_base1(self):
# For positive base arrays, indices are indexing elements directly (in absolute terms)
# rather than relative form the base.
# Negative indices are indexing relative form the end.

# 1-based 2x2 matrix
arr = System.Array.CreateInstance(str, (2,2), (1,1))

Expand Down Expand Up @@ -267,6 +298,38 @@ def test_base1(self):
self.assertEqual(arr.GetValue(System.Array[System.Int32]((2,1))), "b_2,1")
self.assertEqual(arr.GetValue(System.Array[System.Int32]((2,2))), "b_2,2")

def test_base_negative(self):
# For negative base arrays, negative indices are indexing elements directly (like non negative indices)
# rather than indexing relative from the end.

# 2-dim array [-1, 0, 1] x [-1, 0, 1]
arr = System.Array.CreateInstance(str, (3,3), (-1,-1))
for i in range(-1, 2):
for j in range(-1, 2):
arr[i, j] = "a_%d,%d" % (i, j)

for i in range(-1, 2):
for j in range(-1, 2):
self.assertEqual(arr[i, j], "a_%d,%d" % (i, j))

# test that VauleError is raised when the index is out of range
self.assertRaises(IndexError, lambda: arr[-2, 0])
self.assertRaises(IndexError, lambda: arr[2, 0])
self.assertRaises(IndexError, lambda: arr[0, -2])
self.assertRaises(IndexError, lambda: arr[0, 2])

# test slice indexing
# 1-dim array [-1, 0, 1]
arr1 = System.Array.CreateInstance(int, (3,), (-1,))
for i in range(-1, 2):
arr1[i] = i
self.assertEqual(arr1[-1:1], System.Array[int]((-1, 0)))
self.assertEqual(arr1[-2:1], System.Array[int]((-1, 0)))
self.assertEqual(arr1[0:], System.Array[int]((0, 1)))
self.assertEqual(arr1[:1], System.Array[int]((-1, 0)))
self.assertEqual(arr1[:], System.Array[int]((-1, 0, 1)))
self.assertEqual(arr1[:-2], System.Array[int](0))

def test_array_type(self):

def type_helper(array_type, instance):
Expand Down

0 comments on commit b6a28dc

Please sign in to comment.