在python中旋转列表最有效的方法是什么? 现在我有这样的东西:

>>> def rotate(l, n):
...     return l[n:] + l[:n]
... 
>>> l = [1,2,3,4]
>>> rotate(l,1)
[2, 3, 4, 1]
>>> rotate(l,2)
[3, 4, 1, 2]
>>> rotate(l,0)
[1, 2, 3, 4]
>>> rotate(l,-1)
[4, 1, 2, 3]

有没有更好的办法?


当前回答

我能想到的最简单的方法:

a.append(a.pop(0))

其他回答

Jon Bentley在Programming Pearls(第2专栏)中描述了一个优雅而高效的算法,用于将n元素向量x向左旋转i个位置:

让我们把这个问题看作是把数组ab转换成数组 Ba,但我们也假设我们有一个函数,它与 数组的指定部分中的元素。从ab开始 反转a得到arb,反转b得到 Arbr,然后反转整个 得到(arbr)r, 就是。这将产生以下代码 旋转: 反向张(0) 反向(n - 1),我 反向(0,n - 1)

这可以被翻译成Python:

def rotate(x, i):
    i %= len(x)
    x[:i] = reversed(x[:i])
    x[i:] = reversed(x[i:])
    x[:] = reversed(x)
    return x

演示:

>>> def rotate(x, i):
...     i %= len(x)
...     x[:i] = reversed(x[:i])
...     x[i:] = reversed(x[i:])
...     x[:] = reversed(x)
...     return x
... 
>>> rotate(list('abcdefgh'), 1)
['b', 'c', 'd', 'e', 'f', 'g', 'h', 'a']
>>> rotate(list('abcdefgh'), 3)
['d', 'e', 'f', 'g', 'h', 'a', 'b', 'c']
>>> rotate(list('abcdefgh'), 8)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> rotate(list('abcdefgh'), 9)
['b', 'c', 'd', 'e', 'f', 'g', 'h', 'a']

我不知道这是否“有效”,但它也有效:

x = [1,2,3,4]
x.insert(0,x.pop())

编辑:再次你好,我刚刚发现这个解决方案的一个大问题! 考虑下面的代码:

class MyClass():
    def __init__(self):
        self.classlist = []

    def shift_classlist(self): # right-shift-operation
        self.classlist.insert(0, self.classlist.pop())

if __name__ == '__main__':
    otherlist = [1,2,3]
    x = MyClass()

    # this is where kind of a magic link is created...
    x.classlist = otherlist

    for ii in xrange(2): # just to do it 2 times
        print '\n\n\nbefore shift:'
        print '     x.classlist =', x.classlist
        print '     otherlist =', otherlist
        x.shift_classlist() 
        print 'after shift:'
        print '     x.classlist =', x.classlist
        print '     otherlist =', otherlist, '<-- SHOULD NOT HAVE BIN CHANGED!'

shift_classlist()方法执行的代码与我的x.insert(0,x.pop())-solution相同,otherlist是一个独立于类的列表。在将otherlist的内容传递给MyClass之后。Classlist列表,调用shift_classlist()也会改变otherlist列表:

控制台输出:

before shift:
     x.classlist = [1, 2, 3]
     otherlist = [1, 2, 3]
after shift:
     x.classlist = [3, 1, 2]
     otherlist = [3, 1, 2] <-- SHOULD NOT HAVE BIN CHANGED!



before shift:
     x.classlist = [3, 1, 2]
     otherlist = [3, 1, 2]
after shift:
     x.classlist = [2, 3, 1]
     otherlist = [2, 3, 1] <-- SHOULD NOT HAVE BIN CHANGED!

我使用Python 2.7。我不知道这是不是一个错误,但我认为更有可能是我误解了这里的一些东西。

有人知道为什么会这样吗?

关于时间安排的一些注意事项:

如果从列表开始,l.append(l.pop(0))是可以使用的最快方法。这可以单独用时间复杂度来证明:

双端队列。旋转是O(k) (k=元素数量) 列表到deque的转换是O(n) 列表。附加和列表。pop都是O(1)

所以如果你从deque对象开始,你可以deque.rotate(),代价是O(k)。但是,如果起始点是一个列表,那么使用deque.rotate()的时间复杂度是O(n)。l.append(l.pop(0)在O(1)处更快。

为了便于说明,这里有一些1M迭代的计时示例:

需要类型转换的方法:

双端队列。使用deque对象旋转:0.12380790710449219秒(最快) 双端队列。旋转类型转换:6.853878974914551秒 np。滚动nparray: 6.0491721630096436秒 np。滚带类型转换:27.558452129364014秒

列出这里提到的方法:

L.append (l.pop(0)): 0.32483696937561035秒(最快) "shiftInPlace": 4.819645881652832秒 ...

所用的计时代码如下。


collections.deque

显示从列表创建deques是O(n):

from collections import deque
import big_o

def create_deque_from_list(l):
     return deque(l)

best, others = big_o.big_o(create_deque_from_list, lambda n: big_o.datagen.integers(n, -100, 100))
print best

# --> Linear: time = -2.6E-05 + 1.8E-08*n

如果你需要创建deque对象:

1M次迭代@ 6.853878974914551秒

setup_deque_rotate_with_create_deque = """
from collections import deque
import random
l = [random.random() for i in range(1000)]
"""

test_deque_rotate_with_create_deque = """
dl = deque(l)
dl.rotate(-1)
"""
timeit.timeit(test_deque_rotate_with_create_deque, setup_deque_rotate_with_create_deque)

如果你已经有deque对象:

1M次迭代@ 0.12380790710449219秒

setup_deque_rotate_alone = """
from collections import deque
import random
l = [random.random() for i in range(1000)]
dl = deque(l)
"""

test_deque_rotate_alone= """
dl.rotate(-1)
"""
timeit.timeit(test_deque_rotate_alone, setup_deque_rotate_alone)

np.roll

如果你需要创建nparray

1百万次迭代@ 27.558452129364014秒

setup_np_roll_with_create_npa = """
import numpy as np
import random
l = [random.random() for i in range(1000)]
"""

test_np_roll_with_create_npa = """
np.roll(l,-1) # implicit conversion of l to np.nparray
"""

如果你已经有nparray:

1M次迭代@ 6.0491721630096436秒

setup_np_roll_alone = """
import numpy as np
import random
l = [random.random() for i in range(1000)]
npa = np.array(l)
"""

test_roll_alone = """
np.roll(npa,-1)
"""
timeit.timeit(test_roll_alone, setup_np_roll_alone)

“转移到位”

不需要类型转换

1M次迭代@ 4.819645881652832秒

setup_shift_in_place="""
import random
l = [random.random() for i in range(1000)]
def shiftInPlace(l, n):
    n = n % len(l)
    head = l[:n]
    l[:n] = []
    l.extend(head)
    return l
"""

test_shift_in_place="""
shiftInPlace(l,-1)
"""

timeit.timeit(test_shift_in_place, setup_shift_in_place)

l.append(l.pop(0))

不需要类型转换

1M迭代@ 0.32483696937561035

setup_append_pop="""
import random
l = [random.random() for i in range(1000)]
"""

test_append_pop="""
l.append(l.pop(0))
"""
timeit.timeit(test_append_pop, setup_append_pop)

下面的方法是O(n)到位,辅助内存不变:

def rotate(arr, shift):
  pivot = shift % len(arr)
  dst = 0
  src = pivot
  while (dst != src):
    arr[dst], arr[src] = arr[src], arr[dst]
    dst += 1
    src += 1
    if src == len(arr):
      src = pivot
    elif dst == pivot:
      pivot = src

请注意,在python中,这种方法与其他方法相比效率非常低,因为它不能利用任何部分的本机实现。

deque对两端的拉和推进行了优化。它们甚至有一个专用的rotate()方法。

from collections import deque
items = deque([1, 2])
items.append(3)        # deque == [1, 2, 3]
items.rotate(1)        # The deque is now: [3, 1, 2]
items.rotate(-1)       # Returns deque to original state: [1, 2, 3]
item = items.popleft() # deque == [2, 3]