Обращение произвольного среза в 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] Они равны;-)

Другие вопросы по тегам