在python中反转任意slice

Reversing an arbitrary slice in Python

我正在寻找一个关于如何在Python中反转切片的通用方法。我读了这篇综合性的文章,其中有几个关于切片如何工作的很好的解释:了解python的切片表示法

然而,我无法找到一个关于如何计算反向切片的通用规则,反向切片以相反的顺序处理完全相同的元素。事实上,我很惊讶没有找到一个内置的方法来实现这一点。

我要找的是一种方法reversed_slice,它与任意startstopstep值(包括负值)类似:

1
2
3
4
5
6
7
>>> 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])

我试过但不起作用的是:

1
2
3
4
5
6
7
8
9
10
11
12
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重合时,这是很好的。对于其他情况,它不:

1
2
3
4
5
6
7
>>> 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▼显示)。这不是一个选项,因为我需要反转切片本身。原因是我在h5py数据集上工作,这些数据集不允许片中存在负的step值。

  • 一种简单但不是很优雅的方法是使用坐标列表,即a[list(reversed(range(*s.indices(len(a)))))]。由于h5py要求清单中的指数必须按递增顺序给出,因此这也不是一种选择。


可以为step指定负值。

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

因此,您可以如下构建reversed_slice函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> 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])
>>>


我只是想用这个问题的答案,但是当测试发现仍然有一些情况会默默地给出错误的结果。-

以下从其他答案发展而来的逆向切片函数的定义似乎正确地涵盖了这些情况。-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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)


我找到了一个基于Sunitha答案的有效解决方案(编辑:也实现了Warwick的答案):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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条款避免了边界问题。

此解决方案仅在提供要寻址的序列长度时有效。我觉得没有办法解决这个问题。如果有,或者有更简单的方法,我愿意接受建议!


到目前为止,我也没有找到任何内置的,但什么工作,即使是消极的步骤:

1
2
3
4
5
6
7
8
9
10
11
12
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)

这给了你

1
2
3
4
>>> import numpy as np
>>> a = np.arange(30)
>>> s = np.s_[24:10:-1]
>>> expected = list(reversed(a[s]))

[18、16、14、12、10]

1
2
>>> # resulting slice
>>> result = invert_slice(s.start, s.stop, s.step)

切片(18,8,-2)

1
2
>>> assert np.allclose(expected, a[result]),"Invalid Slice %s" % result
>>> a[result]

[18 16 14 12 10]它们是相等的;-)


你在start/stop数学方面犯了一些错误:

1
2
3
4
5
6
7
8
9
10
11
12
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

一旦你把它放到你的代码中,一切都会正常工作。