Обращение произвольного среза в Python
Я ищу общий метод о том, как обратить вспять срез в Python. Я прочитал этот исчерпывающий пост, в котором есть несколько хороших объяснений того, как работает нарезка: Понимание нотации среза Python
Тем не менее, я не могу понять обобщенное правило о том, как рассчитать обратный срез, который обращается к точно таким же элементам в обратном порядке. Я был на самом деле удивлен, что не нашел встроенного метода, делающего это.
То, что я ищу, это метод reversed_slice
который работает так с произвольным start
, stop
а также step
значения, включая отрицательные значения:
>>> 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])
То, что я пробовал, но не работает, это:
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)
Это прекрасно работает для шага 1
и когда последний адресуемый элемент совпадает с stop-1
, Для других случаев это не:
>>> 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])
Так что, похоже, мне не хватает некоторых отношений, как (stop - start) % step
, Любая помощь о том, как написать общий метод, очень ценится.
Заметки:
Я знаю, что есть другие возможности получить последовательность с теми же элементами, например, вызов
reversed(a[s])
, Это не вариант здесь, так как мне нужно перевернуть сам срез. Причина в том, что я работаю надh5py
наборы данных, которые не допускают отрицательныхstep
значения в срезах.Простым, но не очень элегантным способом было бы использование списков координат, т.е.
a[list(reversed(range(*s.indices(len(a)))))]
, Это также не вариант из-заh5py
Требование, чтобы индексы в списке давались в порядке возрастания.
4 ответа
Я нашел рабочее решение на основе ответа Sunitha:
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_)
m = (instop - instart) % instep or instep
if instep > 0 and instart - m < 0:
outstop = None
else:
outstop = instart - m
if instep < 0 and instop - m > len_:
outstart = None
else:
outstart = instop - m
return slice(outstart, outstop, -instep)
Он использует slice.indices(len)
метод для расширения функциональности, так что он также может быть использован с None
записи в срез, например, с [::-1]
, Проблемы на границах избегаются с if
статьи.
Это решение работает только при указании длины последовательности для адресации. Я не думаю, что есть способ обойти это. Если есть, или если есть более простой способ, я открыт для предложений!
Вы можете указать отрицательные значения для step
,
>>> s = np.s_[20-2:10-2:-2]
>>> a[s]
array([18, 16, 14, 12, 10])
Таким образом, вы можете построить reversed_slice
функционировать следующим образом
>>> def reversed_slice(s):
... """
... Reverses a slice
... """
... m = (s.stop-s.start) % s.step or s.step
... return slice(s.stop-m, s.start-m, -s.step)
...
>>> a = np.arange(30)
>>> s = np.s_[10:20:2]
>>> a[reversed_slice(s)]
array([18, 16, 14, 12, 10])
>>>
>>> a[reversed_slice(reversed_slice(s))]
array([10, 12, 14, 16, 18])
>>>
Я просто хотел использовать ответ на этот вопрос, но во время тестирования обнаружил, что есть еще некоторые случаи, которые молча дают неправильный результат -
Следующее определение функции reversed_slice, разработанное на основе других ответов, похоже, правильно охватывает эти случаи:
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)
Вы сделали несколько ошибок с start/stop
математика:
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
step = -instep
Как только вы поместите это в свой код, все должно работать просто отлично.
До сих пор я также не нашел ничего встроенного, но что работало даже для негативных шагов:
def invert_slice(start, stop, step=1):
distance = stop - start
step_distance = distance // step
expected_distance = step_distance * step
if expected_distance != distance:
expected_distance += step
new_start = start + expected_distance - step
new_stop = start - step
return slice(new_start, new_stop, -step)
Это дает вам
>>> import numpy as np
>>> a = np.arange(30)
>>> s = np.s_[24:10:-1]
>>> expected = list(reversed(a[s]))
[18, 16, 14, 12, 10]
>>> # resulting slice
>>> result = invert_slice(s.start, s.stop, s.step)
срез (18, 8, -2)
>>> assert np.allclose(expected, a[result]), "Invalid Slice %s" % result
>>> a[result]
[18 16 14 12 10] Они равны;-)