J'ai récemment étudié comment les différentes structures de données sont implémentées dans Python afin de rendre mon code plus efficace. En étudiant le fonctionnement des listes et des deques, j'ai constaté que je pouvais obtenir des avantages lorsque je veulent décaler et rétrograder en réduisant le temps de O(n) dans les listes à O(1) en deques (les listes étant implémentées comme des tableaux de longueur fixe qui doivent être copiés complètement chaque fois que quelque chose est inséré à l'avant, etc ...). Ce que je n'arrive pas à trouver sont les détails de la façon dont un deque est implémenté, et les détails de ses inconvénients par rapport aux listes. Quelqu'un peut-il éclairer moi sur ces deux questions?
https://hg.python.org/cpython/file/3.5/Modules/_collectionsmodule.c
Un
dequeobject
est composé d'une liste doublement liée deblock
nœuds.
Donc oui, un deque
est une liste chaînée (doublement) comme une autre réponse le suggère.
Élaboration: ce que cela signifie, c'est que les listes Python sont bien meilleures 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 sauter les choses des extrémités, avec l'indexation (mais pas le découpage, fait intéressant) étant possible mais plus lente qu'avec les listes.
Check-out collections.deque
. De la documentation:
Deques prend en charge les ajouts et les sauts de mémoire de thread-safe, efficaces de chaque côté du deque avec approximativement les mêmes performances O(1) dans les deux sens).
Bien que les objets de liste prennent en charge des opérations similaires, ils sont optimisés pour des opérations rapides de longueur fixe et entraînent O(n) coûts de déplacement de la mémoire pour les opérations pop (0) et insert (0, v) qui modifient les deux la taille et la position de la représentation sous-jacente des données.
Comme il est dit, l'utilisation de pop (0) ou de insert (0, v) entraîne des pénalités importantes avec les objets de liste. Vous ne pouvez pas utiliser d'opérations tranche/index sur un deque
, mais vous pouvez utiliser popleft
/appendleft
, qui sont des opérations deque
est optimisé pour. Voici une référence 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
L'entrée de documentation pour deque
objets énonce la plupart de ce que vous devez savoir, je suppose. Citations notables:
Deques prend en charge les ajouts et les sauts de mémoire de thread-safe, efficaces de chaque côté du deque avec approximativement les mêmes performances 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.
Il faudrait que je jette un œil à la source pour savoir si l'implémentation est une liste chaînée ou autre, mais il me semble qu'une deque
a à peu près les mêmes caractéristiques qu'une liste doublement liée.
En plus de toutes les autres réponses utiles, ici est un peu plus d'informations comparant la complexité temporelle (Big-Oh) de diverses opérations sur Python listes, deques, sets, et les dictionnaires. Cela devrait aider à sélectionner la bonne structure de données pour un problème particulier.
Alors que je ne sais pas exactement comment Python l'a implémenté, j'ai écrit ici une implémentation de files d'attente utilisant uniquement des tableaux. Elle 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 __== '__main__':
main()
Cela ne fonctionnera pas aussi rapidement que l'implémentation C, mais il utilise la même logique.