[python] 파이썬의 deques는 어떻게 구현되며 언제 목록보다 더 나쁩니 까?

최근에 코드를보다 효율적으로 만들기 위해 다양한 데이터 구조가 Python에서 구현되는 방식을 조사했습니다. 목록과 deques의 작동 방식을 조사한 결과, 목록의 O (n)에서 deques의 O (1)로 시간을 단축하거나 이동을 해제하고 싶을 때 이점을 얻을 수 있음을 발견했습니다 (목록은 다음과 같은 고정 길이 배열로 구현 됨). 전면에 무언가를 삽입 할 때마다 완전히 복사됩니다. 내가 찾을 수없는 것은 deque가 어떻게 구현되는지에 대한 세부 사항과 그 단점과 목록의 세부 사항입니다. 누군가이 두 가지 질문에 대해 가르쳐 줄 수 있습니까?



답변

https://github.com/python/cpython/blob/v3.8.1/Modules/_collectionsmodule.c

A dequeobject는 이중으로 연결된 block노드 목록으로 구성 됩니다.

그래서 예, deque다른 답변에서 알 수 있듯이 a 는 (의심스러운) 연결된 목록입니다.

정교함 : 이것이 의미하는 것은 Python 목록이 슬라이싱을 포함한 임의 액세스 및 고정 길이 작업에 훨씬 더 나은 반면, 데크는 인덱싱 (하지만 슬라이싱은 아님)을 사용하여 끝 부분을 밀어 내고 터뜨리는 데 훨씬 더 유용하다는 것입니다. 가능하지만 목록보다 느립니다.


답변

확인하십시오 collections.deque. 문서에서 :

Deques는 어느 방향 으로든 거의 동일한 O (1) 성능으로 deque의 양쪽에서 스레드로부터 안전한 메모리 효율적인 추가 및 팝을 지원합니다.

목록 객체는 유사한 작업을 지원하지만 빠른 고정 길이 작업에 최적화되어 있으며 기본 데이터 표현의 크기와 위치를 모두 변경하는 pop (0) 및 insert (0, v) 작업에 대해 O (n) 메모리 이동 비용이 발생합니다. .

말했듯이 pop (0) 또는 insert (0, v)를 사용하면 목록 객체에 큰 불이익이 발생합니다. 에서 슬라이스 / 인덱스 작업을 deque사용할 수 없지만 작업 이 최적화 된 popleft/ 를 사용할 수 있습니다 . 다음은이를 입증하는 간단한 벤치 마크입니다.appendleftdeque

import time
from collections import deque

num = 100000

def append(c):
    for i in range(num):
        c.append(i)

def appendleft(c):
    if isinstance(c, deque):
        for i in range(num):
            c.appendleft(i)
    else:
        for i in range(num):
            c.insert(0, i)
def pop(c):
    for i in range(num):
        c.pop()

def popleft(c):
    if isinstance(c, deque):
        for i in range(num):
            c.popleft()
    else:
        for i in range(num):
            c.pop(0)

for container in [deque, list]:
    for operation in [append, appendleft, pop, popleft]:
        c = container(range(num))
        start = time.time()
        operation(c)
        elapsed = time.time() - start
        print "Completed %s/%s in %.2f seconds: %.1f ops/sec" % (container.__name__, operation.__name__, elapsed, num / elapsed)

내 컴퓨터의 결과 :

Completed deque/append in 0.02 seconds: 5582877.2 ops/sec
Completed deque/appendleft in 0.02 seconds: 6406549.7 ops/sec
Completed deque/pop in 0.01 seconds: 7146417.7 ops/sec
Completed deque/popleft in 0.01 seconds: 7271174.0 ops/sec
Completed list/append in 0.01 seconds: 6761407.6 ops/sec
Completed list/appendleft in 16.55 seconds: 6042.7 ops/sec
Completed list/pop in 0.02 seconds: 4394057.9 ops/sec
Completed list/popleft in 3.23 seconds: 30983.3 ops/sec


답변

deque객체에 대한 문서 항목은 알아야 할 대부분의 내용을 설명합니다. 주목할만한 인용구 :

Deques는 어느 방향 으로든 거의 동일한 O (1) 성능으로 deque의 양쪽에서 스레드로부터 안전한 메모리 효율적인 추가 및 팝을 지원합니다.

그러나…

인덱스 액세스는 양쪽 끝에서 O (1)이지만 중간에서 O (n)으로 느려집니다. 빠른 임의 액세스를 위해 대신 목록을 사용하십시오.

구현이 연결 목록인지 아니면 다른 것인지 확인하기 위해 소스를 살펴보아야하지만, 마치 deque이중 연결 목록과 거의 동일한 특성이있는 것처럼 들립니다 .


답변

다른 모든 도움이 답변 외에도, 여기 파이썬 목록, deques, 세트 및 사전에 다양한 작업의 시간 복잡도 (빅 – 오)를 비교 좀 더 정보입니다. 이는 특정 문제에 적합한 데이터 구조를 선택하는 데 도움이됩니다.


답변

파이썬이 어떻게 구현했는지 정확히 모르겠지만 여기에서는 배열 만 사용하여 큐 구현을 작성했습니다. Python의 큐와 복잡성이 동일합니다.

class ArrayQueue:
""" Implements a queue data structure """

def __init__(self, capacity):
    """ Initialize the queue """

    self.data = [None] * capacity
    self.size = 0
    self.front = 0

def __len__(self):
    """ return the length of the queue """

    return self.size

def isEmpty(self):
    """ return True if the queue is Empty """

    return self.data == 0

def printQueue(self):
    """ Prints the queue """

    print self.data

def first(self):
    """ Return the first element of the queue """

    if self.isEmpty():
        raise Empty("Queue is empty")
    else:
        return self.data[0]

def enqueue(self, e):
    """ Enqueues the element e in the queue """

    if self.size == len(self.data):
        self.resize(2 * len(self.data))
    avail = (self.front + self.size) % len(self.data)
    self.data[avail] = e
    self.size += 1

def resize(self, num):
    """ Resize the queue """

    old = self.data
    self.data = [None] * num
    walk = self.front
    for k in range(self.size):
        self.data[k] = old[walk]
        walk = (1+walk)%len(old)
    self.front = 0

def dequeue(self):
    """ Removes and returns an element from the queue """

    if self.isEmpty():
        raise Empty("Queue is empty")
    answer = self.data[self.front]
    self.data[self.front] = None
    self.front = (self.front + 1) % len(self.data)
    self.size -= 1
    return answer

class Empty(Exception):
""" Implements a new exception to be used when stacks are empty """

pass

그리고 여기에서 몇 가지 코드로 테스트 할 수 있습니다.

def main():
""" Tests the queue """

Q = ArrayQueue(5)
for i in range(10):
    Q.enqueue(i)
Q.printQueue()
for i in range(10):
    Q.dequeue()
Q.printQueue()


if __name__ == '__main__':
    main()

C 구현만큼 빠르게 작동하지는 않지만 동일한 논리를 사용합니다.


답변