Programing

파이썬에서 목록을 회전시키는 효율적인 방법

lottogame 2020. 4. 9. 07:58
반응형

파이썬에서 목록을 회전시키는 효율적인 방법


파이썬에서 목록을 회전시키는 가장 효율적인 방법은 무엇입니까? 지금 나는 이와 같은 것을 가지고있다 :

>>> 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 collections.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]

그냥 사용하는 것은 pop(0)어떻습니까?

list.pop([i])

목록에서 지정된 위치에있는 항목을 제거하고 반환하십시오. 인덱스를 지정하지 않으면 a.pop()목록의 마지막 항목을 제거하고 반환합니다. ( i메소드 서명에서 대괄호 는 매개 변수가 선택적이며 해당 위치에 대괄호를 입력하지 않아야 함을 나타냅니다.이 표기법은 Python Library Reference에 자주 표시됩니다.)


Numpy는 다음 roll명령을 사용하여이를 수행 할 수 있습니다 .

>>> import numpy
>>> a=numpy.arange(1,10) #Generate some data
>>> numpy.roll(a,1)
array([9, 1, 2, 3, 4, 5, 6, 7, 8])
>>> numpy.roll(a,-1)
array([2, 3, 4, 5, 6, 7, 8, 9, 1])
>>> numpy.roll(a,5)
array([5, 6, 7, 8, 9, 1, 2, 3, 4])
>>> numpy.roll(a,9)
array([1, 2, 3, 4, 5, 6, 7, 8, 9])

이 작업을 수행 할 때 수행하려는 작업에 따라 다릅니다.

>>> shift([1,2,3], 14)

다음을 변경하고 싶을 수도 있습니다.

def shift(seq, n):
    return seq[n:]+seq[:n]

에:

def shift(seq, n):
    n = n % len(seq)
    return seq[n:] + seq[:n]

별도의 데이터 구조를 구성하는 대신 이러한 요소 세트를 반복하려는 경우 반복자를 사용하여 생성기 표현식을 구성하십시오.

def shift(l,n):
    return itertools.islice(itertools.cycle(l),n,n+len(l))

>>> list(shift([1,2,3],1))
[2, 3, 1]

내가 생각할 수있는 가장 간단한 방법 :

a.append(a.pop(0))

또한 목록을 제자리로 이동 (변경)하거나 함수가 새 목록을 반환하도록할지 여부에 따라 다릅니다. 내 테스트에 따르면 다음과 같은 것이 두 가지 목록을 추가하는 구현보다 20 배 이상 빠르기 때문입니다.

def shiftInPlace(l, n):
    n = n % len(l)
    head = l[:n]
    l[:n] = []
    l.extend(head)
    return l

실제로, l = l[:]전달 된 목록의 사본을 조작하기 위해 맨 위에 a 추가해도 여전히 두 배 빠릅니다.

http://gist.github.com/288272 에서 일부 타이밍을 사용한 다양한 구현


타이밍에 대한 몇 가지 참고 사항 :

목록으로 시작하는 l.append(l.pop(0))경우 가장 빠른 방법입니다. 시간 복잡성만으로도이를 보여줄 수 있습니다.

  • deque.rotate는 O (k)입니다 (k = 요소 수).
  • 변환을 해제하는 목록은 O (n)입니다.
  • list.append와 list.pop은 모두 O (1)입니다.

따라서 deque객체 로 시작하는 경우 deque.rotate()O (k)를 희생 할 수 있습니다 . 그러나 시작점이 목록이면 사용의 시간 복잡도 deque.rotate()는 O (n)입니다. l.append(l.pop(0)O (1)에서 더 빠릅니다.

설명을 위해 1M 반복에 대한 샘플 타이밍이 있습니다.

타입 변환이 필요한 메소드 :

  • deque.rotatedeque 객체로 : 0.12380790710449219 초 (가장 빠름)
  • deque.rotate유형 변환 사용시 : 6.853878974914551 초
  • np.rollnparray 사용시 : 6.0491721630096436 초
  • np.roll유형 변환 사용시 : 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 객체가있는 경우 :

0.12380790710449219 초에서 1M 반복

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를 만들어야하는 경우

1M 반복 @ 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가있는 경우 :

6.0491721630096436 초에서 1M 반복

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)

"제자리에 이동"

타입 변환이 필요하지 않습니다

4.819645881652832 초에서 1M 반복

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. 첨부 (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)

나는 이것에 관심이 있었고 제안 된 솔루션 중 일부를 perfplot (작은 프로젝트) 과 비교했습니다 .

그것은 밝혀졌다

for _ in range(n):
    data.append(data.pop(0))

이다 훨씬 작은 변화를위한 가장 빠른 방법 n.

큰 들어 n,

data[n:] + data[:n]

나쁘지 않습니다.

본질적으로 perfplot은 큰 배열을 늘리기 위해 이동을 수행하고 시간을 측정합니다. 결과는 다음과 같습니다.

shift = 1:

여기에 이미지 설명을 입력하십시오

shift = 100:

여기에 이미지 설명을 입력하십시오


줄거리를 재현하는 코드 :

import numpy
import perfplot
import collections


shift = 100


def list_append(data):
    return data[shift:] + data[:shift]


def shift_concatenate(data):
    return numpy.concatenate([data[shift:], data[:shift]])


def roll(data):
    return numpy.roll(data, -shift)


def collections_deque(data):
    items = collections.deque(data)
    items.rotate(-shift)
    return items


def pop_append(data):
    for _ in range(shift):
        data.append(data.pop(0))
    return data


perfplot.save(
    "shift100.png",
    setup=lambda n: numpy.random.rand(n).tolist(),
    kernels=[list_append, roll, shift_concatenate, collections_deque, pop_append],
    n_range=[2 ** k for k in range(7, 20)],
    logx=True,
    logy=True,
    xlabel="len(data)",
)

아마도 링 버퍼가 더 적합 할 것입니다. 목록은 아니지만 목적에 따라 목록처럼 충분히 작동 할 수 있습니다.

문제는리스트의 시프트 효율이 O (n)이며, 이는 충분히 큰리스트에 대해 중요해진다는 것입니다.

링 버퍼에서 쉬프트는 단순히 O (1) 인 헤드 위치를 업데이트하는 것입니다.


변경 불가능한 구현의 경우 다음과 같이 사용할 수 있습니다.

def shift(seq, n):
    shifted_seq = []
    for i in range(len(seq)):
        shifted_seq.append(seq[(i-n) % len(seq)])
    return shifted_seq

print shift([1, 2, 3, 4], 1)

효율성이 목표라면 (주기? 메모리?) 배열 모듈을 보는 것이 좋습니다. http://docs.python.org/library/array.html

배열에는 목록의 오버 헤드가 없습니다.

그러나 순수한 목록에 관한 한, 당신이 원하는 것은 당신이 할 수있는만큼 좋은 것입니다.


나는 당신이 이것을 찾고 있다고 생각합니다 :

a.insert(0, x)

다른 대안 :

def move(arr, n):
    return [arr[(idx-n) % len(arr)] for idx,_ in enumerate(arr)]

이 비용 모델을 참조로 사용합니다.

http://scripts.mit.edu/~6.006/fall07/wiki/index.php?title=Python_Cost_Model

목록을 분할하고 두 개의 하위 목록을 연결하는 방법은 선형 시간 작업입니다. 상수 시간 작업 인 pop을 사용하는 것이 좋습니다.

def shift(list, n):
    for i in range(n)
        temp = list.pop()
        list.insert(0, temp)

이것이 '효율적인지'모르지만 작동합니다.

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 ()) 솔루션과 동일한 코드를 실행하고 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!

파이썬 2.7을 사용합니다. 그게 버그인지는 모르겠지만 여기서 뭔가를 잘못 이해했을 가능성이 높습니다.

왜 이런 일이 일어나는지 아십니까?


다음 방법은 보조 메모리가 일정한 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

파이썬 에서이 접근법은 다른 조각에 비해 끔찍하게 비효율적입니다. 어떤 조각의 기본 구현을 활용할 수 없기 때문입니다.


나는 비슷한 것을 가지고 있습니다. 예를 들어, 2만큼 이동하려면 ...

def Shift(*args):
    return args[len(args)-2:]+args[:len(args)-2]

가장 효율적인 방법이 있다고 생각합니다

def shift(l,n):
    n = n % len(l)  
    return l[-U:] + l[:-U]

리스트 X = ['a', 'b', 'c', 'd', 'e', 'f']shift 리스트 길이보다 작은 원하는 시프트 값을 위해, 함수 list_shift()를 아래와 같이 정의 할 수 있습니다

def list_shift(my_list, shift):
    assert shift < len(my_list)
    return my_list[shift:] + my_list[:shift]

list_shift(X,1)반품 ['b', 'c', 'd', 'e', 'f', 'a'] list_shift(X,3)반품['d', 'e', 'f', 'a', 'b', 'c']


사용 사례는 무엇입니까? 실제로 완전히 시프트 된 어레이가 필요하지 않은 경우가 있습니다. 시프트 된 어레이의 소수의 요소에만 액세스하면됩니다.

파이썬 슬라이스를 얻는 것은 런타임 O (k)입니다. 여기서 k는 슬라이스이므로 슬라이스 회전은 런타임 N입니다. deque rotation 명령도 O (k)입니다. 더 잘할 수 있을까요?

매우 큰 배열을 가정 해 봅시다 (예를 들어, 계산하기가 느리면 슬라이스 속도가 느립니다). 다른 해결책은 원래 배열을 그대로두고 어떤 종류의 이동 후 원하는 색인에 존재했을 항목의 색인을 간단히 계산하는 것입니다.

시프트 된 요소에 액세스하면 O (1)이됩니다.

def get_shifted_element(original_list, shift_to_left, index_in_shifted):
    # back calculate the original index by reversing the left shift
    idx_original = (index_in_shifted + shift_to_left) % len(original_list)
    return original_list[idx_original]

my_list = [1, 2, 3, 4, 5]

print get_shifted_element(my_list, 1, 2) ----> outputs 4

print get_shifted_element(my_list, -2, 3) -----> outputs 2 

다음 함수는 전송 된 목록을 임시 목록에 복사하므로 pop 함수는 원래 목록에 영향을 미치지 않습니다.

def shift(lst, n, toreverse=False):
    templist = []
    for i in lst: templist.append(i)
    if toreverse:
        for i in range(n):  templist = [templist.pop()]+templist
    else:
        for i in range(n):  templist = templist+[templist.pop(0)]
    return templist

테스트 :

lst = [1,2,3,4,5]
print("lst=", lst)
print("shift by 1:", shift(lst,1))
print("lst=", lst)
print("shift by 7:", shift(lst,7))
print("lst=", lst)
print("shift by 1 reverse:", shift(lst,1, True))
print("lst=", lst)
print("shift by 7 reverse:", shift(lst,7, True))
print("lst=", lst)

산출:

lst= [1, 2, 3, 4, 5]
shift by 1: [2, 3, 4, 5, 1]
lst= [1, 2, 3, 4, 5]
shift by 7: [3, 4, 5, 1, 2]
lst= [1, 2, 3, 4, 5]
shift by 1 reverse: [5, 1, 2, 3, 4]
lst= [1, 2, 3, 4, 5]
shift by 7 reverse: [4, 5, 1, 2, 3]
lst= [1, 2, 3, 4, 5]

Programming Pearls (Column 2)의 Jon Bentley n요소 벡터 xi위치 별로 왼쪽으로 회전시키는 우아하고 효율적인 알고리즘을 설명합니다 .

배열 ab배열 변환하는 것으로 문제를 ba보자. 그러나 배열 의 지정된 부분에서 요소를 뒤집는 함수가 있다고 가정하자. 시작 ab, 우리는 반대로 a얻기 위해 얻기 위해 얻을 수있는 모든 일을 반대로 다음과 정확히 일치하는 . 결과적으로 다음과 같은 회전 코드가 생성됩니다.arbbarbr(arbr)rba

reverse(0, i-1)
reverse(i, n-1)
reverse(0, n-1)

이것은 다음과 같이 파이썬으로 번역 될 수 있습니다 :

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']

다른 언어의 시프트와 유사한 기능 :

def shift(l):
    x = l[0]
    del(l[0])
    return x

참고 URL : https://stackoverflow.com/questions/2150108/efficient-way-to-rotate-a-list-in-python

반응형