Implémenter des files d'attente et des piles efficaces – Real Python

By | août 23, 2021

Expert Python

Si vous travaillez souvent avec des listes en Python, vous savez probablement qu'elles ne fonctionnent pas assez rapidement lorsque vous en avez besoin. pop et ajouter éléments sur leur extrémité gauche. Python collections module fournit une classe appelée deque qui est spécialement conçu pour fournir des moyens rapides et économes en mémoire pour ajouter et extraire des éléments des deux extrémités de la structure de données sous-jacente.

Python deque est une file d'attente à deux extrémités de bas niveau et hautement optimisée qui est utile pour implémenter des files d'attente et des piles élégantes, efficaces et pythoniques, qui sont les types de données de type liste les plus courants en informatique.

Dans ce tutoriel, vous apprendrez :

  • Comment créer et utiliser Python deque dans ton code
  • Comment efficacement ajouter et pop articles aux deux extrémités d'un deque
  • Comment utiliser deque construire efficacement files d'attente et piles
  • Quand cela vaut la peine d'être utilisé deque à la place de liste

Pour mieux comprendre ces sujets, vous devez connaître les bases de l'utilisation des listes Python. Il vous sera également bénéfique d'avoir une compréhension générale des files d'attente et des piles.

Enfin, vous rédigerez quelques exemples qui vous guideront à travers certains cas d'utilisation courants de deque, qui est l'un des types de données les plus puissants de Python.

Premiers pas avec Python deque

L'ajout d'éléments et leur suppression à partir de l'extrémité droite d'une liste Python sont normalement des opérations efficaces. Si vous utilisez la notation Big O pour la complexité temporelle, alors vous pouvez dire qu'ils sont O(1). Cependant, lorsque Python a besoin de réaffecter de la mémoire pour augmenter la liste sous-jacente pour accepter de nouveaux éléments, ces opérations sont plus lentes et peuvent devenir O(m).

De plus, l'ajout et la suppression d'éléments à l'extrémité gauche d'une liste Python sont connus pour être des opérations inefficaces avec O(m) la vitesse.

Étant donné que les listes Python fournissent les deux opérations avec .ajouter() et .pop(), ils sont utilisables comme piles et files d'attente. Cependant, les problèmes de performances que vous avez rencontrés auparavant peuvent affecter considérablement les performances globales de vos applications.

Python deque a été le premier type de données ajouté au collections module de retour dans Python 2.4. Ce type de données a été spécialement conçu pour surmonter les problèmes d'efficacité de .ajouter() et .pop() dans la liste Python.

Les deques sont des types de données de type séquence conçus comme une généralisation de piles et files d'attente. Ils prennent en charge les opérations d'ajout et de suppression rapides et efficaces en mémoire aux deux extrémités de la structure de données.

Opérations d'ajout et de suppression aux deux extrémités d'un deque object sont stables et tout aussi efficaces car deques sont implémentés sous la forme d'une liste doublement chaînée. De plus, les opérations d'ajout et de suppression sur les demandes sont également thread-safe et économe en mémoire. Ces fonctionnalités rendent deques particulièrement utile pour créer des piles et des files d'attente personnalisées en Python.

Les deques sont également la voie à suivre si vous devez conserver une liste des articles vus pour la dernière fois, car vous pouvez restreindre la longueur maximale de vos deques. Si vous le faites, une fois qu'un deque est plein, il supprime automatiquement les éléments d'une extrémité lorsque vous ajoutez de nouveaux éléments à l'autre extrémité.

Voici un résumé des principales caractéristiques de deque:

  • Stocke des éléments de tout type de données
  • Est un type de données modifiable
  • Prend en charge les opérations d'adhésion avec le dans opérateur
  • Prend en charge l'indexation, comme dans a_deque[i]
  • Ne prend pas en charge le tranchage, comme dans a_deque[0:2]
  • Prend en charge les fonctions intégrées qui fonctionnent sur des séquences et des itérables, tels que len(), trié(), renversé(), et plus
  • Ne prend pas en charge le tri sur place
  • Prend en charge l'itération normale et inverse
  • Prend en charge le décapage avec cornichon
  • Assure des opérations pop et d'ajout rapides, efficaces en mémoire et thread-safe aux deux extrémités

Création deque instances est un processus simple. Vous avez juste besoin d'importer deque de collections et appelez-le avec un facultatif itérable comme argument :

>>>

>>> de collections importer deque

>>> # Créer un deque vide
>>> deque()
deque([])

>>> # Utilisez différents itérables pour créer des demandes
>>> deque((1, 2, 3, 4))
deque([1, 2, 3, 4])

>>> deque([[[[1, 2, 3, 4])
deque([1, 2, 3, 4])

>>> deque(gamme(1, 5))
deque([1, 2, 3, 4])

>>> deque("a B c d")
deque(['a', 'b', 'c', 'd'])

>>> Nombres = "une": 1, "deux": 2, "Trois": 3, "quatre": 4
>>> deque(Nombres.clés())
deque(['one', 'two', 'three', 'four'])

>>> deque(Nombres.valeurs())
deque([1, 2, 3, 4])

>>> deque(Nombres.éléments())
deque([('one', 1), ('two', 2), ('three', 3), ('four', 4)])

Si vous instanciez deque sans fournir un itérable comme argument, alors vous obtenez un deque vide. Si vous fournissez et entrez itérable, alors deque initialise la nouvelle instance avec ses données. L'initialisation va de gauche à droite en utilisant deque.append().

Les deque initializer prend les deux arguments facultatifs suivants :

  1. itérable contient un itérable qui fournit les données d'initialisation.
  2. maxlen contient un nombre entier qui spécifie la longueur maximale du deque.

Comme mentionné précédemment, si vous ne fournissez pas de itérable, alors vous obtenez un deque vide. Si vous fournissez une valeur à maxlen, alors votre deque ne stockera que jusqu'à maxlen éléments.

Enfin, vous pouvez également utiliser des itérables non ordonnés, tels que des ensembles, pour initialiser vos demandes. Dans ces cas, vous n'aurez pas de commande prédéfinie pour les articles dans la demande finale.

Popping et ajout d'éléments efficacement

La différence la plus importante entre deque et liste est que le premier vous permet d'effectuer des opérations d'ajout et de suppression efficaces aux deux extrémités de la séquence. Les deque classe implémente dédié .popleft() et .appendleft() méthodes qui opèrent directement à l'extrémité gauche de la séquence :

>>>

>>> de collections importer deque

>>> Nombres = deque([[[[1, 2, 3, 4])
>>> Nombres.popleft()
1
>>> Nombres.popleft()
2
>>> Nombres
deque([3, 4])

>>> Nombres.ajouter à gauche(2)
>>> Nombres.ajouter à gauche(1)
>>> Nombres
deque([1, 2, 3, 4])

Ici, vous utilisez .popleft() et .appendleft() pour supprimer et ajouter des valeurs, respectivement, à l'extrémité gauche de Nombres. Ces méthodes sont spécifiques à la conception de deque, et vous ne les trouverez pas dans liste.

Juste comme liste, deque fournit également .ajouter() et .pop() méthodes pour opérer à l'extrémité droite de la séquence. Cependant, .pop() se comporte différemment :

>>>

>>> de collections importer deque

>>> Nombres = deque([[[[1, 2, 3, 4])
>>> Nombres.pop()
4

>>> Nombres.pop(0)
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
Erreur-type: pop() ne prend aucun argument (1 donné)

Ici, .pop() supprime et renvoie la dernière valeur dans le deque. La méthode ne prend pas un index comme argument, vous ne pouvez donc pas l'utiliser pour supprimer des éléments arbitraires de vos demandes. Vous ne pouvez l'utiliser que pour supprimer et retourner l'élément le plus à droite.

Comme vous l'avez appris plus tôt, deque est mis en œuvre comme un liste doublement chaînée. Ainsi, chaque élément d'un deque donné contient une référence (pointeur) vers l'élément suivant et précédent de la séquence.

Les listes doublement liées permettent d'ajouter et de faire apparaître des éléments à partir des opérations légères et efficaces. C'est possible car seuls les pointeurs doivent être mis à jour. En conséquence, les deux opérations ont des performances similaires, O(1). Ils sont également prévisibles en termes de performances, car il n'est pas nécessaire de réaffecter de la mémoire et de déplacer des éléments existants pour en accepter de nouveaux.

L'ajout et la suppression d'éléments à partir de l'extrémité gauche d'une liste Python standard nécessite de déplacer tous les éléments, ce qui finit par être un O(m) opération. De plus, l'ajout d'éléments à l'extrémité droite d'une liste nécessite souvent que Python réaffecte la mémoire et copie les éléments actuels vers le nouvel emplacement de mémoire. Après cela, il peut ajouter les nouveaux éléments. Ce processus prend plus de temps et l'opération d'ajout passe de l'état O(1) à O(m).

Considérez les tests de performances suivants pour ajouter des éléments à l'extrémité gauche d'une séquence, deque vs liste:

# time_append.py

de collections importer deque
de temps importer compteur_perf

FOIS = 10_000
une liste = []
a_deque = deque()

déf temps moyen(fonction, fois):
    le total = 0.0
    pour je dans gamme(fois):
        début = compteur_perf()
        fonction(je)
        le total += (compteur_perf() - début) * 1e9
    revenir le total / fois

liste_heure = temps moyen(lambda je: une liste.insérer(0, je), FOIS)
deque_time = temps moyen(lambda je: a_deque.ajouter à gauche(je), FOIS)
Gain = liste_heure / deque_time

imprimer(F"liste.insert()      liste_heure:.6    ns")
imprimer(F"deque.appendleft() deque_time:.6    ns (Gain:.6x plus vite)")

Dans ce scénario, temps moyen() calcule le temps moyen d'exécution d'une fonction (fonction) un nombre donné de fois prend. Si vous exécutez le script à partir de votre ligne de commande, vous obtenez le résultat suivant :

$ python time_append.py
list.insert() 3735.08 ns
deque.appendleft() 238,889 ns (15,6352x plus rapide)

Dans cet exemple précis, .appendleft() sur un deque est plusieurs fois plus rapide que .insérer() sur un liste. Noter que deque.appendleft() est O(1), ce qui signifie que le temps d'exécution est constant. Cependant, liste.insert() à l'extrémité gauche de la liste se trouve O(m), ce qui signifie que le temps d'exécution dépend du nombre d'éléments à traiter.

Dans cet exemple, si vous incrémentez la valeur de FOIS, alors vous obtiendrez des mesures de temps plus élevées pour liste.insert() mais des résultats stables (constants) pour deque.appendleft(). Si vous souhaitez essayer un test de performance similaire sur les opérations pop pour les demandes et les listes, vous pouvez développer le bloc d'exercice ci-dessous et comparer vos résultats à Python réelc'est après que vous ayez terminé.

A titre d'exercice, vous pouvez modifier le script ci-dessus pour chronométrer deque.popleft() vs liste.pop(0) opérations et estimer leurs performances.

Voici un script qui teste les performances de deque.popleft() et liste.pop(0) opérations :

# time_pop.py

de collections importer deque
de temps importer compteur_perf

FOIS = 10_000
une liste = [[[[1] * FOIS
a_deque = deque(une liste)

déf temps moyen(fonction, fois):
    le total = 0.0
    pour _ dans gamme(fois):
        début = compteur_perf()
        fonction()
        le total += (compteur_perf() - début) * 1e9
    revenir le total / fois

liste_heure = temps moyen(lambda: une liste.pop(0), FOIS)
deque_time = temps moyen(lambda: a_deque.popleft(), FOIS)
Gain = liste_heure / deque_time

imprimer(F"liste.pop(0)     liste_heure:.6    ns")
imprimer(F"deque.popleft() deque_time:.6    ns (Gain:.6x plus vite)")

Si vous exécutez ce script sur votre ordinateur, vous obtiendrez une sortie semblable à la suivante :

liste.pop(0) 2002.08 ns
deque.popleft() 326.454 ns (6.13282x plus rapide)

De nouveau, deque est plus rapide que liste lorsqu'il s'agit de supprimer des éléments de l'extrémité gauche de la séquence sous-jacente. Essayez de changer la valeur de FOIS et voyez ce qui se passe !

Les deque Le type de données a été conçu pour garantir des opérations d'ajout et de suppression efficaces à chaque extrémité de la séquence. Il est idéal pour aborder des problèmes nécessitant la mise en œuvre de structures de données de file d'attente et de pile en Python.

Accéder à des éléments aléatoires dans un deque

Python deque renvoie des séquences mutables qui fonctionnent de manière assez similaire aux listes. En plus de vous permettre d'ajouter et de supprimer efficacement des éléments à partir de leurs extrémités, deques fournit un groupe de méthodes de type liste et d'autres opérations de type séquence pour travailler avec des éléments à des emplacements arbitraires. En voici quelques uns:

Vous pouvez utiliser ces méthodes et techniques pour travailler avec des éléments à n'importe quelle position à l'intérieur d'un deque objet. Voici comment procéder :

>>>

>>> de collections importer deque

>>> des lettres = deque("abde")

>>> des lettres.insérer(2, "c")
>>> des lettres
deque(['a', 'b', 'c', 'd', 'e'])

>>> des lettres.supprimer("ré")
>>> des lettres
deque(['a', 'b', 'c', 'e'])

>>> des lettres[[[[1]
'b'

>>> del des lettres[[[[2]
>>> des lettres
deque(['a', 'b', 'e'])

Ici, vous insérez d'abord "c" dans des lettres au poste 2. Ensuite tu enlèves "ré" du deque en utilisant .supprimer(). Deques permettent également indexage pour accéder aux éléments, que vous utilisez ici pour accéder "b" à l'index 1. Enfin, vous pouvez utiliser le del mot-clé pour supprimer tous les éléments existants d'un deque. Noter que .supprimer() vous permet de supprimer des éléments par valeur, tandis que del supprime des éléments par indice.

Bien que deque les objets prennent en charge l'indexation, ils ne prennent pas en charge tranchage. En d'autres termes, vous ne pouvez pas extraire une tranche d'un deque existant en utilisant la syntaxe de slicing, [start:stop:step], comme vous le feriez avec une liste normale :

>>>

>>> de collections importer deque

>>> Nombres = deque([[[[1, 2, 3, 4, 5])

>>> Nombres[[[[1:3]
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
Erreur-type: l'index de séquence doit être un entier, pas une "tranche"

Deques prend en charge l'indexation, mais il est intéressant de noter qu'ils ne prennent pas en charge le découpage en tranches. Lorsque vous essayez d'obtenir une part d'un deque, vous obtenez un Erreur-type. En général, effectuer un découpage sur une liste chaînée serait inefficace, donc l'opération n'est pas disponible.

Jusqu'à présent, vous l'avez vu deque est assez similaire à liste. Cependant, alors que liste est basé sur des tableaux, deque est basé sur une liste doublement chaînée.

Il y a un coût caché derrière deque étant implémenté sous forme de liste doublement chaînée : accéder, insérer et supprimer des éléments arbitraires ne sont pas des opérations efficaces. Pour les exécuter, l'interprète doit parcourir le deque jusqu'à ce qu'il atteigne l'élément souhaité. Alors, ils sont O(m) à la place de O(1) opérations.

Voici un script qui montre comment les demandes et les listes se comportent lorsqu'il s'agit de travailler avec des éléments arbitraires :

# time_random_access.py

de collections importer deque
de temps importer compteur_perf

FOIS = 10_000
une liste = [[[[1] * FOIS
a_deque = deque(une liste)

déf temps moyen(fonction, fois):
    le total = 0.0
    pour _ dans gamme(fois):
        début = compteur_perf()
        fonction()
        le total += (compteur_perf() - début) * 1e6
    revenir le total / fois

déf time_it(séquence):
    milieu = longueur(séquence) // 2
    séquence.insérer(milieu, "milieu")
    séquence[[[[milieu]
    séquence.supprimer("milieu")
    del séquence[[[[milieu]

liste_heure = temps moyen(lambda: time_it(une liste), FOIS)
deque_time = temps moyen(lambda: time_it(a_deque), FOIS)
Gain = deque_time / liste_heure

imprimer(F"liste  liste_heure:.6    s (Gain:.6x plus vite)")
imprimer(F"deque deque_time:.6    s")

Ce script programme l'insertion, la suppression et l'accès à des éléments au milieu d'une demande et d'une liste. Si vous exécutez le script, vous obtenez une sortie qui ressemble à la suivante :

$ python time_random_access.py
liste 63,8658 s (1,44517x plus rapide)
deque 92.2968 s

Les deques ne sont pas des structures de données à accès aléatoire comme les listes. Par conséquent, accéder aux éléments depuis le milieu d'un deque est moins efficace que de faire la même chose sur une liste. Le principal point à retenir ici est que les demandes ne sont pas toujours plus efficaces que les listes.

Python deque est optimisé pour les opérations à chaque extrémité de la séquence, ils sont donc toujours meilleurs que les listes à cet égard. D'un autre côté, les listes sont meilleures pour les opérations à accès aléatoire et de longueur fixe. Voici quelques différences entre les demandes et les listes en termes de performances :

Opération deque liste
Accéder à des éléments arbitraires via l'indexation O(m) O(1)
Popping et ajout d'éléments sur l'extrémité gauche O(1) O(m)
Faire sauter et ajouter des éléments à l'extrémité droite O(1) O(1) + réaffectation
Insérer et supprimer des éléments au milieu O(m) O(m)

Dans le cas des listes, .ajouter() a des performances amorties affectées par la réallocation de mémoire lorsque l'interpréteur doit augmenter la liste pour accepter de nouveaux éléments. Cette opération nécessite de copier tous les éléments actuels vers le nouvel emplacement mémoire, ce qui affecte considérablement les performances.

Ce résumé peut vous aider à choisir le type de données approprié pour le problème à résoudre. Cependant, assurez-vous de profiler votre code avant de passer des listes aux demandes. Les deux ont leurs points forts en termes de performances.

Construire des files d'attente efficaces avec deque

Comme vous l'avez déjà appris, deque est implémenté comme une file d'attente à deux extrémités qui fournit une généralisation de piles et files d'attente. Dans cette section, vous apprendrez à utiliser deque pour implémenter vos propres types de données abstraits de file d'attente (ADT) à un niveau bas d'une manière élégante, efficace et Pythonic.

Les files d'attente sont des collections d'éléments. Vous pouvez modifier les files d'attente en ajoutant des éléments à une extrémité et en supprimant des éléments à l'autre extrémité.

Les files d'attente gèrent leurs articles dans un Premier entré, premier sorti mode (FIFO). Ils fonctionnent comme un tuyau où vous insérez de nouveaux articles à une extrémité du tuyau et sortez les anciens articles de l'autre extrémité. L'ajout d'un élément à une extrémité d'une file d'attente est appelé file d'attente opération. La suppression d'un élément de l'autre extrémité s'appelle file d'attente.

Pour mieux comprendre les files d'attente, prenez votre restaurant préféré comme exemple. Le restaurant a une file d'attente de personnes qui attendent une table pour commander leur nourriture. En règle générale, la dernière personne à arriver se tiendra à la fin de la file d'attente. La personne au début de la file d'attente la quittera dès qu'une table sera disponible.

Voici comment vous pouvez émuler le processus à l'aide d'un deque objet:

>>>

>>> de collections importer deque

>>> les clients = deque()

>>> # Personnes arrivant
>>> les clients.ajouter("Jeanne")
>>> les clients.ajouter("John")
>>> les clients.ajouter(" Linda ")

>>> les clients
deque(['Jane', 'John', 'Linda'])

>>> # Personnes recevant des tables
>>> les clients.popleft()
'Jeanne'
>>> les clients.popleft()
'John'
>>> les clients.popleft()
'Linda'

>>> # Aucune personne dans la file d'attente
>>> les clients.popleft()
Traceback (appel le plus récent en dernier) :
  Déposer "", ligne 1, dans 
IndexError: pop d'un deque vide

Ici, vous créez d'abord un vide deque objet pour représenter la file d'attente des personnes arrivant au restaurant. Pour mettre une personne en file d'attente, vous utilisez .ajouter(), qui ajoute des éléments individuels à l'extrémité droite. Pour retirer une personne de la file d'attente, vous utilisez .popleft(), qui supprime et renvoie des éléments individuels à l'extrémité gauche d'un deque.

Frais! Votre simulation de file d'attente fonctionne ! Cependant, depuis deque est une généralisation, son API ne correspond pas à l'API de file d'attente typique. Par exemple, au lieu de .enqueue(), vous avez .ajouter(). Vous avez aussi .popleft() à la place de .dequeue(). En outre, deque fournit plusieurs autres opérations qui pourraient ne pas répondre à vos besoins spécifiques.

La bonne nouvelle est que vous pouvez créer des classes de file d'attente personnalisées avec les fonctionnalités dont vous avez besoin et rien d'autre. Pour ce faire, vous pouvez utiliser en interne un deque pour stocker les données et fournir la fonctionnalité souhaitée dans vos files d'attente personnalisées. Vous pouvez le considérer comme une implémentation du modèle de conception d'adaptateur, dans lequel vous convertissez l'interface du deque en quelque chose qui ressemble plus à une interface de file d'attente.

Par exemple, supposons que vous ayez besoin d'un type de données abstrait de file d'attente personnalisé qui offre uniquement les fonctionnalités suivantes :

  • Éléments en file d'attente
  • Supprimer des éléments
  • Renvoyer la longueur de la file d'attente
  • Soutenir les tests d'adhésion
  • Prise en charge de l'itération normale et inverse
  • Fournir une représentation de chaîne conviviale

Dans ce cas, vous pouvez rédiger un File d'attente classe qui ressemble à ce qui suit :

# custom_queue.py

de collections importer deque

classer File d'attente:
    déf __init__(soi):
        soi._éléments = deque()

    déf file d'attente(soi, Objet):
        soi._éléments.ajouter(Objet)

    déf file d'attente(soi):
        essayer:
            revenir soi._éléments.popleft()
        sauf IndexError:
            augmenter IndexError("sortir d'une file d'attente vide") de Rien

    déf __len__(soi):
        revenir longueur(soi._éléments)

    déf __contient__(soi, Objet):
        revenir Objet dans soi._éléments

    déf __iter__(soi):
        rendement de soi._éléments

    déf __renversé__(soi):
        rendement de renversé(soi._éléments)

    déf __repr__(soi):
        revenir F"File d'attente(liste(soi._éléments))"

Ici, ._éléments détient un deque objet qui vous permet de stocker et de manipuler les éléments de la file d'attente. File d'attente met en oeuvre .enqueue() à l'aide de deque.append() pour ajouter des éléments à la fin de la file d'attente. Il met également en œuvre .dequeue() avec deque.popleft() pour supprimer efficacement les éléments du début de la file d'attente.

Les méthodes spéciales prennent en charge les fonctionnalités suivantes :

Idéalement, .__repr__() doit renvoyer une chaîne représentant une expression Python valide. Cette expression vous permettra de recréer l'objet sans ambiguïté avec la même valeur.

Cependant, dans l'exemple ci-dessus, l'intention est d'utiliser la valeur de retour de la méthode pour afficher gracieusement l'objet sur le shell interactif. Vous pouvez permettre de construire File d'attente instances de cette représentation de chaîne spécifique en acceptant un itérable d'initialisation comme argument pour .__init__() et construire des instances à partir de celui-ci.

Avec ces derniers ajouts, votre File d'attente la classe est complète. Pour utiliser cette classe dans votre code, vous pouvez procéder comme suit :

>>>

>>> de custom_queue importer File d'attente

>>> Nombres = File d'attente()
>>> Nombres
File d'attente([])

>>> # Mettre les éléments en file d'attente
>>> pour numéro dans gamme(1, 5):
...     Nombres.file d'attente(numéro)
...
>>> Nombres
File d'attente([1, 2, 3, 4])

>>> # Prise en charge de len()
>>> longueur(Nombres)
4

>>> # Soutenir les tests d'adhésion
>>> 2 dans Nombres
Vrai
>>> dix dans Nombres
Faux

>>> # Itération normale
>>> pour numéro dans Nombres:
...     imprimer(F"Nombre: numéro")
...
1
2
3
4

À titre d'exercice, vous pouvez tester les fonctionnalités restantes et implémenter d'autres fonctionnalités, telles que la prise en charge des tests d'égalité, la suppression et l'accès à des éléments aléatoires, etc. Vas-y, essaies!

Explorer d'autres fonctionnalités de deque

En plus des fonctionnalités que vous avez vues jusqu'à présent, deque fournit également d'autres méthodes et attributs spécifiques à leur conception interne. Ils ajoutent de nouvelles fonctionnalités utiles à ce type de données polyvalent.

Dans cette section, vous découvrirez les autres méthodes et attributs fournis par deques, leur fonctionnement et leur utilisation dans votre code.

Limitation du nombre maximum d'articles : maxlen

L'une des fonctionnalités les plus utiles de deque est la possibilité de spécifier le longueur maximale d'un deque donné en utilisant le maxlen argument lorsque vous instanciez la classe.

Si vous fournissez une valeur à maxlen, alors votre deque ne stockera que jusqu'à maxlen éléments. Dans ce cas, vous disposez d'un deque borné. Une fois qu'un deque limité est plein avec le nombre d'éléments spécifié, l'ajout d'un nouvel élément à chaque extrémité supprime et supprime automatiquement l'élément à l'extrémité opposée :

>>>

>>> de collections importer deque

>>> quatre_nombres = deque([[[[0, 1, 2, 3, 4], maxlen=4) # Jeter 0
>>> quatre_nombres
deque([1, 2, 3, 4], maxlen=4)

>>> quatre_nombres.ajouter(5)  # Supprimer automatiquement 1
>>> quatre_nombres
deque([2, 3, 4, 5], maxlen=4)

>>> quatre_nombres.ajouter(6)  # Supprimer automatiquement 2
>>> quatre_nombres
deque([3, 4, 5, 6], maxlen=4)

>>> quatre_nombres.ajouter à gauche(2) # Supprimer automatiquement 6
>>> quatre_nombres
deque([2, 3, 4, 5], maxlen=4)

>>> quatre_nombres.ajouter à gauche(1)  # Supprimer automatiquement 5
>>> quatre_nombres
deque([1, 2, 3, 4], maxlen=4)

>>> quatre_nombres.maxlen
4

Si le nombre d'éléments dans l'itérable d'entrée est supérieur à maxlen, alors deque supprime les éléments les plus à gauche (0 dans l'exemple). Une fois que le deque est plein, l'ajout d'un élément à n'importe quelle extrémité supprime automatiquement l'élément à l'autre extrémité.

Notez que si vous ne spécifiez pas de valeur à maxlen, alors il vaut par défaut Rien, et le deque peut atteindre un nombre arbitraire d'éléments.

Avoir la possibilité de restreindre le nombre maximum d'éléments vous permet d'utiliser des deques pour suivre les derniers éléments dans une séquence donnée d'objets ou d'événements. Par exemple, vous pouvez suivre les cinq dernières transactions sur un compte bancaire, les dix derniers fichiers texte ouverts dans un éditeur, les cinq dernières pages dans un navigateur, etc.

Noter que maxlen est disponible en tant qu'attribut en lecture seule dans vos demandes, ce qui vous permet de vérifier si la demande est pleine, comme dans deque.maxlen == len(deque).

Enfin, vous pouvez définir maxlen à n'importe quel nombre entier positif représentant le nombre maximum d'éléments que vous souhaitez stocker dans un deque spécifique. Si vous fournissez une valeur négative à maxlen, alors vous obtenez un Erreur de valeur.

Rotation des éléments : .tourner()

Une autre caractéristique intéressante des deques est la possibilité de faire pivoter leurs éléments en appelant .tourner() sur un deque non vide. Cette méthode prend un entier m comme argument et fait pivoter les éléments m marches vers la droite. En d'autres termes, il se déplace m articles de l'extrémité droite à l'extrémité gauche de manière circulaire.

La valeur par défaut de m est 1. Si vous fournissez une valeur négative à m, alors la rotation est vers la gauche :

>>>

>>> de collections importer deque

>>> ordinaux = deque([[[["premier", "seconde", "troisième"])

>>> # Faire pivoter les éléments vers la droite
>>> ordinaux.tourner()
>>> ordinaux
deque(['third', 'first', 'second'])

>>> ordinaux.tourner(2)
>>> ordinaux
deque(['first', 'second', 'third'])

>>> # Faire pivoter les éléments vers la gauche
>>> ordinaux.tourner(-2)
>>> ordinaux
deque(['third', 'first', 'second'])

>>> ordinaux.tourner(-1)
>>> ordinaux
deque(['first', 'second', 'third'])

Dans ces exemples, vous faites pivoter ordinaux plusieurs fois en utilisant .tourner() avec différentes valeurs de m. Si vous appelez .tourner() sans argument, alors il s'appuie sur la valeur par défaut de m et fait tourner le deque 1 positionner à droite. Appeler la méthode avec un négatif m vous permet de faire pivoter les éléments vers la gauche.

Ajout de plusieurs éléments à la fois : .extensiongauche()

Comme les listes ordinaires, les deques fournissent un .se déployer() méthode, qui vous permet d'ajouter plusieurs éléments à l'extrémité droite d'un deque à l'aide d'un itérable comme argument. De plus, les deques ont une méthode appelée étendre vers la gauche(), ce qui prend un itérable comme argument et ajoute ses éléments à l'extrémité gauche du deque cible en une seule fois :

>>>

>>> de collections importer deque

>>> Nombres = deque([[[[1, 2])

>>> # Étendre vers la droite
>>> Nombres.se déployer([[[[3, 4, 5])
>>> Nombres
deque([1, 2, 3, 4, 5])

>>> # Étendre vers la gauche
>>> Nombres.étendre vers la gauche([[[[-1, -2, -3, -4, -5])
>>> Nombres
deque([-5, -4, -3, -2, -1, 1, 2, 3, 4, 5])

Appel .extensiongauche() avec un itérable étend la cible deque vers la gauche. Intérieurement, .extensiongauche() effectue une série d'individus .appendleft() opérations qui traitent l'itérable d'entrée de gauche à droite. Cela finit par ajouter les éléments dans l'ordre inverse à l'extrémité gauche du deque cible.

Utilisation des fonctionnalités de type séquence de deque

Les deques étant des séquences mutables, elles implémentent presque toutes les méthodes et opérations communes aux séquences et aux séquences mutables. Jusqu'à présent, vous avez découvert certaines de ces méthodes et opérations, telles que .insérer(), l'indexation, les tests d'adhésion, etc.

Voici quelques exemples d'autres actions que vous pouvez effectuer sur deque objets:

>>>

>>> de collections importer deque

>>> Nombres = deque([[[[1, 2, 2, 3, 4, 4, 5])

>>> # Concaténation
>>> Nombres + deque([[[[6, 7, 8])
deque([1, 2, 2, 3, 4, 4, 5, 6, 7, 8])

>>> # Répétition
>>> Nombres * 2
deque([1, 2, 2, 3, 4, 4, 5, 1, 2, 2, 3, 4, 4, 5])

>>> # Méthodes de séquence courantes
>>> Nombres = deque([[[[1, 2, 2, 3, 4, 4, 5])
>>> Nombres.indice(2)
1
>>> Nombres.compter(4)
2

>>> # Méthodes communes de séquence mutable
>>> Nombres.inverser()
>>> Nombres
deque([5, 4, 4, 3, 2, 2, 1])

>>> Nombres.dégager()
>>> Nombres
deque([])

Vous pouvez utiliser l'opérateur d'addition (+) pour concaténer deux demandes existantes. D'autre part, l'opérateur de multiplication (*) renvoie un nouveau deque équivalent à répéter le deque d'origine autant de fois que vous le souhaitez.

Concernant les autres méthodes de séquençage, le tableau suivant en fournit un résumé :

Méthode La description
.dégager() Supprimer tous les éléments d'un deque.
.copie() Créez une copie superficielle d'un deque.
.count(valeur) Compter le nombre de fois valeur apparaît dans un deque.
.index(valeur) Retourner la position de valeur dans le deque.
.inverser() Inversez les éléments du deque en place puis revenez Rien.

Ici, .indice() peut également prendre deux arguments optionnels : début et arrêter. Ils vous permettent de restreindre la recherche à ces éléments à ou après début et avant arrêter. La méthode soulève une Erreur de valeur si valeur n'apparaît pas dans le deque à portée de main.

Contrairement aux listes, les demandes n'incluent pas de .sorte() méthode pour trier la séquence en place. En effet, le tri d'une liste chaînée serait une opération inefficace. Si jamais vous avez besoin de trier un deque, vous pouvez toujours utiliser trié().

Mettre Python deque En action

Vous pouvez utiliser deques dans de nombreux cas d'utilisation, par exemple pour implémenter des files d'attente, des piles et des tampons circulaires. Vous pouvez également les utiliser pour conserver un historique d'annulation et de rétablissement, mettre en file d'attente les demandes entrantes vers un service Web, conserver une liste des fichiers et des sites Web récemment ouverts, échanger des données en toute sécurité entre plusieurs threads, etc.

Dans les sections suivantes, vous allez coder quelques petits exemples qui vous aideront à mieux comprendre comment utiliser deques dans votre code.

Conserver un historique des pages

Avoir un maxlen restreindre le nombre maximum d'articles fait deque adapté pour résoudre plusieurs problèmes. Par exemple, supposons que vous créez une application qui récupère les données des moteurs de recherche et des sites de médias sociaux. À un moment donné, vous devez garder une trace des trois derniers sites sur lesquels votre application a demandé des données.

Pour résoudre ce problème, vous pouvez utiliser un deque avec un maxlen de 3:

>>>

>>> de collections importer deque

>>> des sites = (
...     "google.com",
...     "yahoo.com",
...     "bing.com"
... )

>>> pages = deque(maxlen=3)
>>> pages.maxlen
3

>>> pour placer dans des sites:
...     pages.ajouter à gauche(placer)
...

>>> pages
deque(['bing.com', 'yahoo.com', 'google.com'], maxlen=3)

>>> pages.ajouter à gauche("facebook.com")
>>> pages
deque(['facebook.com', 'bing.com', 'yahoo.com'], maxlen=3)

>>> pages.ajouter à gauche("twitter.com")
>>> pages
deque(['twitter.com', 'facebook.com', 'bing.com'], maxlen=3)

Dans cet exemple, pages conserve une liste des trois derniers sites visités par votre application. Une fois que pages est plein, l'ajout d'un nouveau site à une extrémité du deque supprime automatiquement le site à l'extrémité opposée. Ce comportement maintient votre liste à jour avec les trois derniers sites que vous avez utilisés.

Notez que vous pouvez définir maxlen à tout entier positif représentant le nombre d'éléments à stocker dans le deque à portée de main. Par exemple, si vous souhaitez conserver une liste de dix sites, vous pouvez définir maxlen à dix.

Partage de données entre les threads

Python deque est également utile lorsque vous codez des applications multithread, comme décrit par Raymond Hettinger, développeur Python et créateur de deque et le collections module:

Le deque .ajouter(), .appendleft(), .pop(), .popleft(), et prêter) les opérations sont thread-safe dans CPython. (La source)

Pour cette raison, vous pouvez ajouter et supprimer en toute sécurité des données des deux extrémités d'un deque en même temps à partir de threads séparés sans risque de corruption de données ou d'autres problèmes associés.

Pour essayer comment deque fonctionne dans une application multithread, lancez votre éditeur de code préféré, créez un nouveau script appelé threads.py, et ajoutez-y le code suivant :

# threads.py

importer enregistrement
importer Aléatoire
importer enfilage
importer temps
de collections importer deque

enregistrement.configuration de base(niveau=enregistrement.INFO, format="%(messages")

déf attendre_secondes(minutes, max):
    temps.dormir(minutes + Aléatoire.Aléatoire() * (max - minutes))

déf produire(file d'attente, Taille):
    tandis que Vrai:
        si longueur(file d'attente) < Taille:
            valeur = Aléatoire.randint(0, 9)
            file d'attente.ajouter(valeur)
            enregistrement.Info("Produit : %ré    -> %s", valeur, str(file d'attente))
        autre:
            enregistrement.Info("La file d'attente est saturée")
        attendre_secondes(0,1, 0,5)

déf consommer(file d'attente):
    tandis que Vrai:
        essayer:
            valeur = file d'attente.popleft()
        sauf IndexError:
            enregistrement.Info("La file d'attente est vide")
        autre:
            enregistrement.Info("Consommé : %ré    -> %s", valeur, str(file d'attente))
        attendre_secondes(0,2, 0,7)

enregistrement.Info("Démarrer les discussions...n")
enregistrement.Info("Appuyez sur Ctrl+C pour interrompre l'exécutionn")

file_partagée = deque()

enfilage.Fil(cible=produire, arguments=(file_partagée, dix)).début()
enfilage.Fil(cible=consommer, arguments=(file_partagée,)).début()

Ici, produire() prend un file d'attente et un Taille comme arguments. Ensuite, il utilise random.randint() dans un tandis que boucle pour produire en continu des nombres aléatoires et les stocker dans un deque global appelé file_partagée. Étant donné que l'ajout d'éléments à un deque est une opération thread-safe, vous n'avez pas besoin d'utiliser un verrou pour protéger les données partagées des autres threads.

La fonction d'assistance wait_seconds() simule que les deux produire() et consommer() représentent des opérations de longue durée. Il renvoie une valeur de temps d'attente aléatoire entre une plage donnée de secondes, minutes et max.

Dans consommer(), tu appelles .popleft() dans une boucle pour récupérer et supprimer systématiquement les données de file_partagée. Vous enveloppez l'appel à .popleft() dans un essayersauf instruction pour gérer les cas dans lesquels la file d'attente partagée est vide.

Notez que pendant que vous avez défini file_partagée dans l'espace de noms global, vous y accédez via des variables locales à l'intérieur produire() et consommer(). Accéder directement à la variable globale serait plus problématique et certainement pas une bonne pratique.

Les deux dernières lignes du script créent et démarrent des threads séparés à exécuter produire() et consommer() en parallèle. Si vous exécutez le script à partir de votre ligne de commande, vous obtiendrez une sortie semblable à la suivante :

$ fils python.py
Discussions de démarrage...

Appuyez sur Ctrl+C pour interrompre l'exécution

Produit : 1 -> deque([1])
Consommé : 1 -> deque([])
La file d'attente est vide
Produit : 3 -> deque([3])
Produit : 0 -> deque([3, 0])
Consommé : 3 -> deque([0])
Consommé : 0 -> deque([])
Produit : 1 -> deque([1])
Produit : 0 -> deque([1, 0])
        ...

Le thread producteur ajoute des nombres à l'extrémité droite du deque partagé, tandis que le thread consommateur consomme des nombres à l'extrémité gauche. Pour interrompre l'exécution du script, vous pouvez appuyer sur Ctrl+C sur votre clavier.

Enfin, vous pouvez jouer un peu avec l'intervalle de temps à l'intérieur produire() et consommer(). Modifiez les valeurs que vous passez à wait_seconds(), et observez le comportement du programme lorsque le producteur est plus lent que le consommateur et inversement.

Émuler le queue Commander

Le dernier exemple que vous allez coder ici émule le queue commande, qui est disponible sur les systèmes d'exploitation Unix et de type Unix. La commande accepte un chemin de fichier sur la ligne de commande et imprime les dix dernières lignes de ce fichier sur la sortie standard du système. Vous pouvez modifier le nombre de lignes dont vous avez besoin queue à imprimer avec le -n, --lignes option.

Voici une petite fonction Python qui émule la fonctionnalité de base de queue:

>>>

>>> de collections importer deque

>>> déf queue(nom de fichier, lignes=dix):
...     essayer:
...         avec ouvert(nom de fichier) comme déposer:
...             revenir deque(déposer, lignes)
...     sauf OSError comme Erreur:
...         imprimer(F« Ouverture du fichier »nom de fichier" a échoué avec l'erreur : Erreur')
...

Ici, vous définissez queue(). Le premier argument, nom de fichier, contient le chemin d'accès au fichier cible sous forme de chaîne. Le deuxième argument, lignes, représente le nombre de lignes que vous souhaitez récupérer à la fin du fichier cible. Noter que lignes par défaut à dix pour simuler le comportement par défaut de queue.

Le deque dans la ligne en surbrillance ne peut stocker que le nombre d'articles que vous passez à lignes. Cela garantit que vous obtenez le nombre de lignes souhaité à la fin du fichier d'entrée.

Comme vous l'avez vu précédemment, lorsque vous créez un deque borné et que vous l'initialisez avec un itérable, il contient plus d'éléments que ce qui est autorisé (maxlen), les deque le constructeur supprime tous les éléments les plus à gauche de l'entrée. À cause de cela, vous vous retrouvez avec le dernier maxlen lignes du fichier cible.

Conclusion

Les files d'attente et les piles sont couramment utilisées types de données abstraits en programmation. Ils nécessitent généralement une efficacité pop et ajouter operations on either end of the underlying data structure. Python’s collections module provides a data type called deque that’s specially designed for fast and memory-efficient append and pop operations on both ends.

Avec deque, you can code your own queues and stacks at a low level in an elegant, efficient, and Pythonic way.

In this tutorial, you learned how to:

  • Create and use Python’s deque in your code
  • Efficiently append and pop items from both ends of a sequence with deque
  • Use deque to build efficient queues and stacks in Python
  • Decide when to use deque instead of list

In this tutorial, you also coded a few examples that helped you approach some common use cases of deque in Python.



[ad_2]