在Python中反转任意切片

3

我正在寻找一种通用的方法来反转Python中的切片。 我阅读了这篇详细的帖子,其中有几个关于切片操作的很好的解释: 理解Python的切片符号

然而,我无法找到一个通用的规则来计算反转的切片,该切片以完全相同的顺序地址相同的元素。我实际上很惊讶没有找到内置的方法来实现这一点。

我要寻找的是一种名为reversed_slice的方法,其工作方式如下,包括负值的任意startstopstep值:

>>> 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]) 来获取具有相同元素反转的序列的方式。但这在此处不是一个选项,因为我需要反转切片本身。原因是我正在使用不允许在切片中使用负数 step 值的 h5py 数据集。 - 一个简单但不太优雅的方式是使用坐标列表,即 a[list(reversed(range(*s.indices(len(a)))))]。由于列表中的索引必须按递增顺序给出,所以这也不是一个选项。
5个回答

3
您可以为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])
>>> 

感谢您的回答。-2 在起始和结束位置是关键所在。它在这种情况下有效,即 (20-10)%2 == 0。然而,在其他情况下,它并不适用:a[11:20:2] 将会返回 array([11, 13, 15, 17, 19]),而 a[20-2:11-2:-2] 将会返回 array([18, 16, 14, 12, 10]) - eaglesear
@eaglesear 感谢您指出。我已经更新了答案。 - Sunitha
@Sunitha 再次感谢你。这仍然不对。使用您更新的版本 reversed_slice(np.s_[10:20:2]) 将得到 slice(20, 10, -2),这将给出 array([20, 18, 16, 14, 12]) - eaglesear
@eaglesear... 啊啊啊... 又更新了。 - Sunitha
@Sunitha 谢谢,这是一个很大的进步,但我仍然发现了一个错误的情况:对于s = np.s_[-2:10:-2]a[s]得到的结果是array([28, 26, 24, 22, 20, 18, 16, 14, 12]),但是reversed_slice(s)得到的结果是slice(12, 0, 2)和一个空数组。 - eaglesear

1

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

以下反向切片函数的定义来自其他答案,似乎可以正确地涵盖这些情况 -

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)

哦,有趣...你能给我一个使用你的切片而不是我的切片的例子吗?我想看看问题出在哪里。 - eaglesear
在对由s.indices返回的instart、instop和instep进行初始测试时,使用测试列表[x for x in range(0,10,1)]。如果没有对s.indices返回的值进行测试或测试(instart==0)或测试(instart==0 and instop==0),则slice[5:0:1]将失败。仅测试(instop==0)slice[5:0:-1]将失败。我发现的问题都是结果应该为[]但实际上不是的情况。 - Warwick
谢谢!我测试了一下,你是对的。我接受了你的答案,因为目前我知道它是不包含任何破坏性情况的。 - eaglesear

0

你在 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

一旦你把这个放进你的代码里,一切都应该正常工作。


感谢您的回答。这是一个进步,但在某些情况下仍会给出错误的结果。使用您的答案:reversed_slice(numpy.s_[20:10:-2],len(a)) 得到 slice(8, 21, 2),对应于 array([ 8, 10, 12, 14, 16, 18, 20]) - eaglesear
@eaglesear,当你复制代码时可能会漏掉一些东西,(20,10,-2) 给出了答案 (12,21,2) -- 在我看来似乎是正确的。我已经检查了几个组合,基本上,这个 reverse_indices() 函数在应用两次时会给出相同的结果,例如 reverse_indices( reverse_indices( ...some values... )) 将会返回那些 ... some values ... - lenik
是的,在这种情况下你是对的。抱歉,其他情况会出错。例如:(10, 21, 1) -> (21, 9, -1)(0, 15, 2) -> (14, -1, -2)(16, None, 2) -> (30, 15, -2) - eaglesear
抱歉,但我找到一个 :) (0, 10, 1) -> (9, -1, -1)。此问题发生在序列的边界处。我单独将其排除,详见我的回答。感谢你们所有人的帮助! - eaglesear
@eaglesear 这是一个边角情况,可以通过简单的 if 语句轻松解决。 - lenik
显示剩余4条评论

0

到目前为止,我也没有找到任何内置的方法,但是以下方法即使对于负步长也有效:

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)

slice(18, 8, -2)

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

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


谢谢!这对于许多情况都有效,甚至适用于负数的起始和结束。就测试而言,它给出了与Sunitha答案相同的切片。所以它无法处理的情况是:对于s = np.s_[-2:10:-2]a[s]产生array([28, 26, 24, 22, 20, 18, 16, 14, 12]),但reversed_slice(s)产生slice(12, 0, 2)和一个空数组。 - eaglesear
对于负数的起始和结束位置,您需要创建一个新的切片类,因为它们取决于数组长度。在子类化时,覆盖indices(length)方法以返回预期范围。 - tlausch

0

我找到了一个可行的解决方案,基于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子句避免了边界问题。

只有在提供序列长度以进行地址处理时,此解决方案才有效。我认为没有办法绕过这个问题。如果有更简单的方法或更好的建议,欢迎提出!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接