Обращение произвольного среза в Python - PullRequest
0 голосов
/ 06 июля 2018

Я ищу общий метод, как перевернуть срез в 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, что индексы в списке должны указываться в возрастающем порядке.

Ответы [ 5 ]

0 голосов
/ 16 марта 2019

Я просто хотел использовать ответ на этот вопрос, но во время тестирования обнаружил, что все еще есть некоторые случаи, которые молча давали бы неправильный результат -

Следующее определение функции 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)
0 голосов
/ 06 июля 2018

Я нашел рабочее решение на основе ответа Sunitha (РЕДАКТИРОВАТЬ: также реализован ответ Warwick):

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)

    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.

Это решение работает только при указании длины последовательности для адресации.Я не думаю, что есть способ обойти это.Если есть, или если есть более простой способ, я открыт для предложений!

0 голосов
/ 06 июля 2018

Вы сделали несколько ошибок с 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

Как только вы вставите это в свой код, все должно работать нормально.

0 голосов
/ 06 июля 2018

До сих пор я также не нашел ни одного встроенного , но что работало даже для отрицательных шагов:

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] Они равны ;-)

0 голосов
/ 06 июля 2018

Вы можете указать отрицательные значения для 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])
>>> 
...