Недавно я начал изучать, как различные структуры данных реализованы в Python, чтобы сделать мой код более эффективным. Изучая, как работают списки и комментарии, я обнаружил, что могу получить преимущества, когда хочу сдвинуть и сменить время, уменьшая время от O (n) в списках до O (1) в deques (списки реализуются как массивы фиксированной длины, которые имеют полностью копироваться каждый раз, когда что-то вставлено спереди и т.д.). То, что я не могу найти, - это особенности того, как реализуется deque, и особенности его недостатков v.s. списки. Может кто-нибудь просветить меня по этим двум вопросам?
Как применяются требования deques в Python, и когда они хуже, чем списки?
Ответ 1
https://hg.python.org/cpython/file/3.5/Modules/_collectionsmodule.c
A
dequeobject
состоит из двусвязного списка узловblock
.
Итак, a deque
- это (двунаправленный) список, как предлагает другой ответ.
Разработка: это означает, что списки Python намного лучше подходят для операций с произвольным доступом и фиксированной длины, включая нарезку, в то время как deques намного полезнее для толкания и выскакивания вещей с конца, с индексированием (но не срезанием, интересно) возможно, но медленнее, чем со списками.
Ответ 2
Отъезд collections.deque
. Из документов:
Deques поддерживает поточную безопасность, память эффективные добавления и всплывающие стороны deque с приблизительно то же значение O (1) в направление.
Хотя объекты списка поддерживают аналогичные операций, они оптимизированы для быстрых операций с фиксированной длиной и O (n) затраты на перемещение памяти для pop (0) и вставить (0, v) операции, которые изменить размер и положение базовое представление данных.
Как он говорит, использование pop (0) или insert (0, v) приводит к большим штрафам с объектами списка. Вы не можете использовать операции среза/индекса на deque
, но вы можете использовать popleft
/appendleft
, для которых оптимизированы операции deque
. Вот простой пример, чтобы продемонстрировать это:
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
Ответ 3
В документации для deque
объектов изложено большинство из того, что вам нужно знать, я подозреваю. Известные цитаты:
Deques поддерживает потокобезопасную, эффективную память, добавляет и выскакивает с обеих сторон дека примерно с той же производительностью O (1) в любом направлении.
Но...
Индексированный доступ - O (1) с обоих концов, но замедляется до O (n) в середине. Для быстрого произвольного доступа используйте списки вместо.
Мне нужно взглянуть на источник, чтобы узнать, является ли реализация связанным списком или чем-то еще, но мне кажется, что deque
имеет примерно те же характеристики, что и двусвязный список.
Ответ 4
В дополнение ко всем другим полезным ответам здесь - это дополнительная информация, сравнивающая временную сложность (Big-Oh) различных операций с Python списки, образцы, наборы и словари. Это должно помочь в выборе правильной структуры данных для конкретной проблемы.
Ответ 5
Пока я не совсем уверен, как это реализовано Python, здесь я написал реализацию очередей с использованием только массивов. Он имеет ту же сложность, что и очереди 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, но использует ту же логику.