Issue
Python handles negative indices so that they subtract from the length. For example, if we have this list xs = [1,2,3,4]
, doing xs[-1]
would give us 4
, the last element, which would be the same as xs[len(xs)-1]
. Now, an easy way to handle this would be the following:
def handle_index(index: int, length: int) -> int:
if index < 0:
return length + index
return index
I tried looking into the source code of CPython and NumPy, but I had a bit of trouble finding where they handled negative indices.
Edit: Python lists and Numpy arrays already handle this. I'm implementing a data structure that uses neither of those, and I would like to know how to efficiently support negative indices.
Solution
In CPython, sliceobject.c
handles converting a slice like start:stop:stride
into bare indices. Negative start
and stop
are handled with code like this:
start = evaluate_slice_index(self->start);
if (start == NULL)
goto error;
if (_PyLong_Sign(start) < 0) {
/* start += length */
PyObject *tmp = PyNumber_Add(start, length);
(Note that the correct calculation in your code would be return length + index
not length - index
, since index
is negative.)
The full code has no shortage of if
statements:
/* Compute slice indices given a slice and length. Return -1 on failure. Used
by slice.indices and rangeobject slicing. Assumes that `len` is a
nonnegative instance of PyLong. */
int
_PySlice_GetLongIndices(PySliceObject *self, PyObject *length,
PyObject **start_ptr, PyObject **stop_ptr,
PyObject **step_ptr)
{
PyObject *start=NULL, *stop=NULL, *step=NULL;
PyObject *upper=NULL, *lower=NULL;
int step_is_negative, cmp_result;
/* Convert step to an integer; raise for zero step. */
if (self->step == Py_None) {
step = _PyLong_GetOne();
Py_INCREF(step);
step_is_negative = 0;
}
else {
int step_sign;
step = evaluate_slice_index(self->step);
if (step == NULL)
goto error;
step_sign = _PyLong_Sign(step);
if (step_sign == 0) {
PyErr_SetString(PyExc_ValueError,
"slice step cannot be zero");
goto error;
}
step_is_negative = step_sign < 0;
}
/* Find lower and upper bounds for start and stop. */
if (step_is_negative) {
lower = PyLong_FromLong(-1L);
if (lower == NULL)
goto error;
upper = PyNumber_Add(length, lower);
if (upper == NULL)
goto error;
}
else {
lower = _PyLong_GetZero();
Py_INCREF(lower);
upper = length;
Py_INCREF(upper);
}
/* Compute start. */
if (self->start == Py_None) {
start = step_is_negative ? upper : lower;
Py_INCREF(start);
}
else {
start = evaluate_slice_index(self->start);
if (start == NULL)
goto error;
if (_PyLong_Sign(start) < 0) {
/* start += length */
PyObject *tmp = PyNumber_Add(start, length);
Py_DECREF(start);
start = tmp;
if (start == NULL)
goto error;
cmp_result = PyObject_RichCompareBool(start, lower, Py_LT);
if (cmp_result < 0)
goto error;
if (cmp_result) {
Py_INCREF(lower);
Py_DECREF(start);
start = lower;
}
}
else {
cmp_result = PyObject_RichCompareBool(start, upper, Py_GT);
if (cmp_result < 0)
goto error;
if (cmp_result) {
Py_INCREF(upper);
Py_DECREF(start);
start = upper;
}
}
}
/* Compute stop. */
if (self->stop == Py_None) {
stop = step_is_negative ? lower : upper;
Py_INCREF(stop);
}
else {
stop = evaluate_slice_index(self->stop);
if (stop == NULL)
goto error;
if (_PyLong_Sign(stop) < 0) {
/* stop += length */
PyObject *tmp = PyNumber_Add(stop, length);
Py_DECREF(stop);
stop = tmp;
if (stop == NULL)
goto error;
cmp_result = PyObject_RichCompareBool(stop, lower, Py_LT);
if (cmp_result < 0)
goto error;
if (cmp_result) {
Py_INCREF(lower);
Py_DECREF(stop);
stop = lower;
}
}
else {
cmp_result = PyObject_RichCompareBool(stop, upper, Py_GT);
if (cmp_result < 0)
goto error;
if (cmp_result) {
Py_INCREF(upper);
Py_DECREF(stop);
stop = upper;
}
}
}
*start_ptr = start;
*stop_ptr = stop;
*step_ptr = step;
Py_DECREF(upper);
Py_DECREF(lower);
return 0;
error:
*start_ptr = *stop_ptr = *step_ptr = NULL;
Py_XDECREF(start);
Py_XDECREF(stop);
Py_XDECREF(step);
Py_XDECREF(upper);
Py_XDECREF(lower);
return -1;
}
Answered By - John Kugelman
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.