Подтвердить что ты не робот

Как применяются требования deques в Python, и когда они хуже, чем списки?

Недавно я начал изучать, как различные структуры данных реализованы в Python, чтобы сделать мой код более эффективным. Изучая, как работают списки и комментарии, я обнаружил, что могу получить преимущества, когда хочу сдвинуть и сменить время, уменьшая время от O (n) в списках до O (1) в deques (списки реализуются как массивы фиксированной длины, которые имеют полностью копироваться каждый раз, когда что-то вставлено спереди и т.д.). То, что я не могу найти, - это особенности того, как реализуется deque, и особенности его недостатков v.s. списки. Может кто-нибудь просветить меня по этим двум вопросам?

4b9b3361

Ответ 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, но использует ту же логику.