Comment les deques en Python sont-ils implémentés, et quand sont-ils pires que les listes?

J'ai récemment étudié comment diverses structures de données sont implémentées en Python afin de rendre mon code plus efficace. En étudiant comment les listes et les deques fonctionnent, j'ai trouvé que je peux obtenir des avantages quand je veux déplacer et défaire en réduisant le temps de O (n) dans les listes à O (1) dans les deques (les listes étant implémentées en tant que tableaux de longueur fixe qui doivent être copiés complètement chaque fois que quelque chose est inséré..). Ce que je n'arrive pas à trouver sont les détails de la façon dont un deque est mis en œuvre, et les spécificités de ses inconvénients v. S. listes. Quelqu'un peut-il m'éclairer sur ces deux questions?

47
demandé sur wim 2011-06-06 23:28:43

5 réponses

Https://hg.python.org/cpython/file/3.5/Modules/_collectionsmodule.c

Un dequeobject est composé d'une double liste chaînée de block nœuds.

Donc oui, un deque est une liste (doublement)liée comme le suggère une autre réponse.

Elaboration: cela signifie que les listes Python sont beaucoup mieux pour les opérations d'accès aléatoire et de longueur fixe, y compris le découpage, tandis que les deques sont beaucoup plus utiles pour pousser et faire éclater les choses, avec indexation (mais pas trancher, intéressant) étant possible mais plus lent qu'avec des listes.

49
répondu JAB 2015-06-16 20:57:06

Découvrez collections.deque. De la docs:

Deques soutien thread-safe, mémoire efficace ajoute et pop de soit côté de la deque avec environ la même performance O(1) dans l'un ou l'autre direction.

Bien que les objets de liste prennent en charge opérations, ils sont optimisés pour opérations de longueur fixe rapides et O (N) coûts de déplacement de la mémoire pour les pop (0) et insert (0, v) opérations qui modifier la taille et la position de le sous-jacent la représentation des données.

Comme il est dit, l'utilisation de pop (0) ou insert (0, v) entraîne de lourdes pénalités avec les objets list. Vous ne pouvez pas utiliser la tranche/index opérations sur un deque, mais vous pouvez utiliser popleft/appendleft, qui sont des opérations deque est optimisé pour. Voici un benchmark simple pour le démontrer:

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)

Résultats sur ma machine:

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
36
répondu zeekay 2011-06-06 20:27:59

L'entrée de documentation pour deque objects décrit la plupart de ce que vous devez savoir, je soupçonne. Citations notables:

Les Deques prennent en charge les ajouts et les pops de chaque côté de la deque avec approximativement la même performance O(1) dans les deux sens.

Mais...

L'accès indexé est O (1) aux deux extrémités mais ralentit à O (n) Au milieu. Pour un accès aléatoire rapide, utilisez plutôt des listes.

Je devrais prendre un regardez la source pour dire si l'implémentation est une liste liée ou autre chose, mais il me semble qu'un deque a à peu près les mêmes caractéristiques qu'une liste doublement liée.

13
répondu senderle 2011-06-06 19:35:56

En plus de toutes les autres réponses utiles, Voici quelques informations supplémentaires comparant la complexité temporelle (Big-Oh) de diverses opérations sur les listes, les déques, les ensembles et les dictionnaires Python. Cela devrait aider à choisir la bonne structure de données pour un problème particulier.

8
répondu PythonJin 2013-03-17 18:01:05

Bien que, je ne suis pas exactement sûr de la façon dont Python l'a implémenté, ici j'ai écrit une implémentation de files d'attente en utilisant uniquement des tableaux. Il a la même complexité que les Files D'attente de 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

Et ici vous pouvez le tester avec du code:

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()

Cela ne fonctionnera pas aussi vite que l'implémentation C, mais il utilise la même logique.

1
répondu TheRevanchist 2015-09-10 14:44:24