优雅的方法交换不等长列表切片?

5

我已经在这个网站上研究了如何交换列表中的元素,但大多数情况都是交换一个元素与另一个元素。

在这里,我试图交换长度不相等的切片位置。 以一个示例列表为例:

x = [6,2,4,3,4,1,4,5]

我希望有一种优雅的方式可以用于交换列表和变量中的值,例如以下形式:
x[0:1], x[1:7] = x[1:7], x[0:1]

#Expected output
x = [2, 4, 3, 4, 1, 4, 5, 6]

毫不意外地,它不能正常工作,而是交换了前两个元素:

#Actual output
x = [2, 6, 4, 3, 4, 1, 4, 5]

另一个例子:交换x[0:4]x[5:7]

#Expected output
x = [1, 4, 4, 6, 2, 4, 3, 5]

希望清楚地表达,交换的方式是将 slice1 的第一个元素占据了 slice2 的第一个元素的前一个位置。其余部分依次类推。

有没有一种简单而优雅且高效的方法来实现这个功能?


我所能想到的唯一“优雅”的方法来支持涉及切片的任意序列赋值是定义一个自定义对象,包装列表,其__set_item__方法将跟踪先前的切片赋值并重新映射后续赋值的索引。使其高效 - 例如相对于分配数量的O(logN) - 将需要一个非平凡的数据结构来跟踪索引转换。 - nickie
4个回答

1
你可以使用collections.deque来旋转数值:
import collections
x = [6,2,4,3,4,1,4,5]
d = collections.deque(x)
d.rotate(-1)
print(d)

输出:

[2, 4, 3, 4, 1, 4, 5, 6]

1
这个问题(在我的理解中)是关于交换不同大小的任意切片。旋转只是其中一个(简单)的子情况。 - nickie
但是这对于第二种情况不起作用,对吧?最后一个值的位置被保留,但它之前的两个元素与前三个元素交换了。@nickie说得很正确。 - Jer J

0

使用列表的简单方法是直接切片连接:

x = [6,2,4,3,4,1,4,5]
x = x[1:] + x[:1]  

print x

输出:

[2, 4, 3, 4, 1, 4, 5, 6]

对于第二种情况,您可以类似地将其分成三个部分。

发布失败的分析:

您尝试的赋值形式是错误的:多重赋值应该是表达式对表达式的,而不是一系列值的流。您输入的内容会被拆分为并行评估和赋值。

x[0:1] = x[1:7]
x[1:7] = x[0:1]

如果你单独执行这些操作,你会发现每种情况下赋值都被限制在较小的片段中。


你错过了一个5,伙计。 - ragardner
这将生成一个新列表。问题(在我的阅读中,再次强调)是关于原地替换的。 - nickie
这个无法通过第二个示例。 - Jer J
@Yukon:谢谢。我没有注意到边界问题。已经修复并改进了。 - Prune

0

我认为没有任何优雅的、完全通用的方法。这里有一种不太优雅的方式:

def swapslices(l, from1, to1, from2, to2):
    if not (from1 <= to1 <= from2 <= to2):
        raise ValueError('slices out of order or overlapping')
    if to1 - from1 == to2 - from2:
        # Easy case. No need to shift the part between the two slices.
        l[from1: to1], l[from2, to2] = l[from2: to2], l[from1: to1]
    else:
        # Hard case. We need to rewrite the whole section.
        l[from1: to2] = l[from2: to2] + l[to1: from2] + l[from1: to1]

这个函数要求您首先传递较低的切片,并且不要尝试使用像6:3这样的切片。

该函数依赖于以下事实:如果您在此处尝试交换切片ac

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    ----  ----  -------
      a     b       c

这样做相当于用 c + b + a 替换整个 a + b + c 部分:

[0, 5, 6, 7, 3, 4, 1, 2, 8, 9]
    -------  ----  ----
        c      b     a

例子:

>>> l = list(range(10))
>>> swapslices(l, 1, 3, 5, 8)
>>> l
[0, 5, 6, 7, 3, 4, 1, 2, 8, 9]

0

我认为这应该足够了:

def swapslices(lst, from1, to1, from2, to2):
    # assumes from1:to1 < from2:to2
    def reverse(l, r):
       r = r - 1
       while l < r:
           lst[l], lst[r] = lst[r], lst[l]
           l += 1
           r -= 1
    reverse(to1, from2)
    reverse(from2, to2)
    reverse(from1, to1)
    reverse(from1, to2)

试着用一下

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
swapslices(a, 2, 4, 6, 9)
# a is now [0, 1, 6, 7, 8, 4, 5, 2, 3, 9, 10]

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