Tuesday, 22 November 2016

Reversing an arbitrary slice in Python




I'm looking for a general method on how to reverse a slice in Python.
I read this comprehensive post, which has several nice explanations about how slicing works:
Understanding Python's slice notation



Yet I cannot figure out a generalized rule on how to calculate a reversed slice which addresses exactly the same elements in reverse order. I was actually surprised not to find a builtin method doing this.



What I'm looking for is a method reversed_slice that works like this with arbitrary start, stop and step values including negative values:



>>> import numpy as np
>>> a = np.arange(30)

>>> s = np.s_[10:20:2]
>>> a[s]
array([10, 12, 14, 16, 18])
>>> a[reversed_slice(s,len(a))]
array([18, 16, 14, 12, 10])


What I've tried but doesn't work is this:



def reversed_slice(slice_, len_):

"""
Reverses a slice (selection in array of length len_),
addressing the same elements in reverse order.
"""
assert isinstance(slice_, slice)
instart, instop, instep = slice_.indices(len_)
if instep > 0:
start, stop, step = instop - 1, instart - 1, -instep
else:
start, stop, step = instop + 1, instart + 1, -instep

return slice(start, stop, step)


This works fine for step of 1 and when the last addressed element coincides with stop-1. For other cases it does not:



>>> import numpy as np
>>> a = np.arange(30)
>>> s = np.s_[10:20:2]
>>> a[s]
array([10, 12, 14, 16, 18])

>>> a[reversed_slice(s,len(a))]
array([19, 17, 15, 13, 11])


So it seems like I'm missing some relation like (stop - start) % step.
Any help on how to write a general method is greatly appreciated.



Notes:





  • I do know that there a other possibilities to get a sequence with the same elements reversed, like calling reversed(a[s]). This is not an option here, as I need to reverse the slice itself. The reason is that I work on h5py datasets which do not allow negative step values in slices.


  • An easy but not very elegant way would be the use of coordinate lists, i.e. a[list(reversed(range(*s.indices(len(a)))))]. This is also not an option due to the h5py requirement that indices in the list must be given in increasing order.



Answer



I just wanted to use the answer to this question but while testing found that there are still some cases that would silently give the wrong result -



The following definition of the reversed_slice function developed from the other answers seems to cover those cases correctly -



def reversed_slice(s, len_):
"""

Reverses a slice selection on a sequence of length len_,
addressing the same elements in reverse order.
"""
assert isinstance(s, slice)
instart, instop, instep = s.indices(len_)

if (instop < instart and instep > 0) or (instop > instart and instep < 0) \
or (instop == 0 and instart == 0) :
return slice(0,0,None)


overstep = abs(instop-instart) % abs(instep)

if overstep == 0 :
overstep = abs(instep)

if instep > 0:
start = instop - overstep
stop = instart - 1
else :
start = instop + overstep

stop = instart + 1

if stop < 0 :
stop = None

return slice(start, stop, -instep)

No comments:

Post a Comment

c++ - Does curly brackets matter for empty constructor?

Those brackets declare an empty, inline constructor. In that case, with them, the constructor does exist, it merely does nothing more than t...