Données Pile Implémentation

Identification

Infoforall

18 - Implémenter une Pile


Nous sommes parvenus à implémenter le type Liste sous la forme de plusieurs structures de données ayant des modèles différents mais ayant les mêmes fonctions d'interface primitives.

  1. Liste contiguë IMMUABLE avec des tableaux statiques : mauvaise idée.
  2. Insertion et suppression LINEAIRE partout.

  3. Liste contiguë MUTABLE avec des tableaux dynamiques, comme le type list de Python :
  4. Insertion et suppression à coût LINEAIRE sur la Tête.

    Insertion et suppression à coût CONSTANT sur la Fin

  5. Liste chaînée IMMUABLE avec un tuple.
  6. Insertion et suppression à coût CONSTANT sur la Tête

    Insertion et suppression à coût LINEAIRE sur la Fin.

  7. Liste chaînée MUTABLE avec des Classes Cellule et une Liste qui pointe uniquement la Tête.
  8. Insertion et suppression à coût CONSTANT sur la Tête

    Insertion et suppression à coût LINEAIRE sur la Fin.

  9. Liste chaînée MUTABLE avec des Classes Cellule et une Liste qui pointe la Tête et la Fin.
  10. Insertion et suppression à coût CONSTANT sur la Tête

    Insertion à coût CONSTANT et suppression à coût LINEAIRE sur la Fin.

Aujourd'hui, nous allons implémenter le type Pile.

Prerequis : l'activité sur la Pile et celles sur les implémentations des Listes.

Documents de cours : open document ou pdf

1 - Implémentation d'une pile avec un modèle tableau dynamique

1.1 Schéma de principe avec la list Python

Pour obtenir des primitives à coût CONSTANT, il faut placer le SOMMET de notre Pile du côté de la Fin du tableau dynamique.

En effet :

  • Les méthodes append(elt) et pop() sont à coût constant.
  • Les méthodes insert(elt, 0) et pop(0) sont à coût linéaire.
  • La fonction len(lst) est à coût constant.
graph LR A("[0]") --- B("[1]") --- C("[2]") -.- D("[n-2]") --- E("[n-1]
Sommet de Pile
Fin de tableau") -. empilement -.- I(("ENTREE")) E -. dépilement -.- F(("SORTIE")) style A fill:#99f,stroke:#333,stroke-width:2px style E fill:#ff9,stroke:#333,stroke-width:2px style B fill:#99f,stroke:#333,stroke-width:2px style C fill:#99f,stroke:#333,stroke-width:2px style D fill:#99f,stroke:#333,stroke-width:2px
(Rappel) 1.2 Pile sous forme de list Python

A - Les primitives désirées pour la Pile MUABLE
  1. nouvelle_pile() -> 'Pile VIDE'
  2. est_pile_vide(p:'Pile') -> bool
  3. empiler(elt:'Elt', p:'Pile') -> None
  4. depiler(p:'Pile NON VIDE') -> 'Elt'
  5. lire_sommet(p:'Pile NON VIDE') -> 'Elt'
  6. taille(p:'Pile') -> int
B - Implémentation avec le type list
  1. nouvelle_pile() -> 'Pile VIDE'
  2. Deux versions possibles.

    >>> p = list()
    >>> p = []

    Coût CONSTANT dans les deux cas.

  3. est_pile_vide(p:'Pile') -> bool
  4. >>> p == [] True

    Coût CONSTANT.

  5. empiler(elt:'Elt', p:'Pile') -> None
  6. >>> p.append(50) # on place 50 au sommet >>> p == [] False >>> p.append(20) # on place 20 au sommet >>> p.append(10) # on place 10 au sommet

    Le sommet contient donc 10, et on trouvera 20 et 50 en dessous.

    Nous avons vu que, dans le cas du tableau dynamique, le coût amorti est CONSTANT.

  7. depiler(p:'Pile NON VIDE') -> 'Elt'
  8. >>> p.pop() # on dépile le sommet 10

    On récupère la valeur de l'ancien sommet. Le sommet est maintenant le 20.

    Nous avons vu que, dans le cas du tableau dynamique, le coût est CONSTANT.

  9. lire_sommet(p:'Pile NON VIDE') -> 'Elt'
  10. On sait que le sommet est à la fin de notre tableau dynamique. Deux versions pour lire ce que contient la dernière case du tableau.

    >>> p[len(p)-1] 20
    >>> p[-1] 20

    Le coût est CONSTANT.

  11. taille(p:'Pile') -> int
  12. >>> len(p) 2

    Le pile contient bien le sommet 20 suivi d'un 50. Deux éléments.

    Le coût est CONSTANT car le nombre d'éléments est stocké directement dans l'objet.

C - Erreurs classiques sur les mutables

La pile est modifiée en place. N'écrivez donc JAMAIS ceci avec une pile mutable :

>>> p = p.append(50) # p contient maintenant None, bravo... >>> p = p.pop() # p n'est plus une pile mais un élément, bravo...
1.3 - Quelques explications sur le contenu interne du tableau

Les instructions correspondent bien à un type mutable : on ne stocke pas la réponse d'empiler() puisque cette réponse est ... None.

>>> a = nouvelle_pile() >>> empiler(a, 5)
Tableau contient 5
>>> empiler(a, 10)
Tableau contient 5-10
>>> empiler(a, 20)
Tableau contient 5-10-20
>>> depiler(a) 20
Tableau contient 5-10

01° Compléter les différentes primitives pour parvenir à implémenter notre Pile MUTABLE avec un modèle tableau dynamique à l'interne.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
"""MODULE d'implémentation de type abstrait PILE MUTABLE avec un modèle TABLEAUX DYNAMIQUES à l'interne La Pile vide sera représentée par un tableau vide []. Le sommet d'une Pile non vide est situé en fin de tableau. Primitives -------------------- ... : nouvelle_pile() -> 'Pile VIDE' ... : est_pile_vide(p:'Pile') -> bool ... : empiler(p:'Pile', elt:'Element') -> None ... : depiler(p:'Pile NON VIDE') -> 'Element' ... : lire_sommet(p:'Pile NON VIDE') -> 'Element' """ def nouvelle_pile() -> 'Pile VIDE': """Renvoie une nouvelle Pile vide""" pass def est_pile_vide(p:'Pile') -> bool: """Prédicat qui renvoie True si p est une Pile vide""" pass def empiler(p:'Pile', elt:'Element' ) -> None: """Modifie la pile p en rajouter elt au sommet""" pass def depiler(p:'Pile NON VIDE') -> 'Element': """Supprime le sommet de la Pile p et renvoie l'élément supprimé""" pass def lire_sommet(p:'Pile NON VIDE') -> 'Element': """Renvoie la valeur actuellement au sommet de la Pile p NON VIDE""" pass if __name__ == '__main__': a = nouvelle_pile() empiler(a, 0) empiler(a, 5) empiler(a, 10) empiler(a, 15) s = lire_sommet(a) assert s == 15 depiler(a) s = lire_sommet(a) assert s == 10

02° Estimer le coût des cinq fonctions primitives.

...CORRECTION...

Difficile de répondre sans connaître les codes des méthodes correspondantes de Python. C'est bien cela le problème de cette façon de faire.

Si on suppose que

  • l'accès à la dernière case avec les crochets est à coût constant,
  • le rajout d'une case en fin de tableau avec append() et
  • la suppression de la dernière case pop() est à coût constant,

alors les opérations sur le sommet (lecture/modification et insertion/suppression) sont toutes à coût constant.

(Bilan Correction) 1.4 Module de Pile MUTABLE avec un modèle list

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
"""MODULE d'implémentation d'une PILE MUTABLE utilisant un modèle TABLEAUX DYNAMIQUES La Pile vide sera représentée par un tableau vide []. Le sommet d'une Pile non vide est situé en fin de tableau. Fonctions primitives -------------------- CST : nouvelle_pile() -> 'Pile VIDE' CST : est_pile_vide(p:'Pile') -> bool CST : empiler(p:'Pile', elt:'Element') -> None CST : depiler(p:'Pile NON VIDE') -> 'Element' CST : lire_sommet(p:'Pile NON VIDE') -> 'Element' """ def nouvelle_pile() -> 'Pile VIDE': """Renvoie une nouvelle Pile vide""" return [] def est_pile_vide(p:'Pile') -> bool: """Prédicat qui renvoie True si p est une Pile vide""" return p == [] def empiler(p:'Pile', elt:'Element' ) -> None: """Modifie la pile p en rajouter elt au sommet""" p.append(elt) # rajoute un élément à la fin du tableau def depiler(p:'Pile NON VIDE') -> 'Element': """Supprime le sommet de la Pile p et renvoie l'élément supprimé""" return p.pop() # supprime et renvoie le dernier élément du tableau def lire_sommet(p:'Pile NON VIDE') -> 'Element': """Renvoie la valeur actuellement au sommet de la Pile p NON VIDE""" return p[len(p)-1] # ou même return p[-1] if __name__ == '__main__': a = nouvelle_pile() empiler(a, 0) empiler(a, 5) empiler(a, 10) empiler(a, 15) s = lire_sommet(a) assert s == 15 depiler(a) s = lire_sommet(a) assert s == 10

MUTABLE : la pile est modifiée directement en mémoire. N'écrivez SURTOUT PAS ceci :
a = empiler(a, 15). Sinon, votre Pile contiendra None après exécution !

03° Utiliser cette implémentation pour constater qu'on a bien une Pile et, qu'à part la mutabilité, on ne peut pas savoir comment tout cela est implémenté à l'interne tant qu'on utilise uniquement les fonctions d'interface.

>>> a = nouvelle_pile() >>> empiler(a, 5) >>> a [5] >>> empiler(a, 10) >>> a [5, 10] >>> empiler(a, 20) >>> a [5, 10, 20] >>> depiler(a) 20 >>> a [5, 10] >>> depiler(a) 10 >>> a [5] >>> empiler(a, 40) >>> a [5, 40] >>> depiler(a) 40 >>> a [5] >>> depiler(a) 5

Notez bien le comportement en PILE : le 40, qu'on rajoute plus tard, sort immédiatement alors qu'il est arrivé bien après : comportement LIFO.

2 - Implémentation d'une Pile IMMUABLE avec comme modèle une liste chaînée de tuples

2.1 Schéma de principe avec une tuple Python

Pour obtenir des primitives à coût CONSTANT, il faut placer le SOMMET de notre Pile du côté de la Tête de la liste chainée.

En effet, les fonctions inserer_tete() et supprimer_tete() sont à coût constant.

Chaque cellule est un tuple de taille 2 dont la structure est
(Valeur du Sommet, Suite de la Pile) ou
(Valeur de Tête, Queue)

graph LR F(("ENTREE")) -.- empilement -.- A("Sommet de Pile
Tête de Liste") --> B("Cellule 1") --> C("Cellle 2") -.-> D("Cellule n-2") ---> E("Cellule n-1 de Fin") I(("SORTIE")) -.- dépilement -.- A style E fill:#99f,stroke:#333,stroke-width:2px style A fill:#ff9,stroke:#333,stroke-width:2px style B fill:#99f,stroke:#333,stroke-width:2px style C fill:#99f,stroke:#333,stroke-width:2px style D fill:#99f,stroke:#333,stroke-width:2px

04° Compléter les différentes primitives pour parvenir à implémenter notre Pile IMMUABLE sous forme du tuple à l'interne.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
"""MODULE d'implémentation de type abstrait PILE IMMUABLE avec un modèle liste chaînée des TUPLES à l'interne La Pile vide sera représentée par un tuple vide (). Le sommet d'une Pile non vide est situé du même côté que la tête de la liste chaînée associée. Fonctions primitives -------------------- ... : nouvelle_pile() -> 'Pile VIDE' ... : est_pile_vide(p:'Pile') -> bool ... : empiler(p:'Pile', elt:'Element') -> 'Pile' ... : depiler(p:'Pile NON VIDE') -> 'Pile' ... : lire_sommet(p:'Pile NON VIDE') -> 'Element' """ def nouvelle_pile() -> 'Pile': """Renvoie une nouvelle Pile vide""" pass def est_pile_vide(p:'Pile') -> bool: """Prédicat qui renvoie True si p est une Pile vide""" pass def empiler(p:'Pile', elt:'Element' ) -> 'Pile': """Renvoie une pile basée sur p mais en rajoutant elt au sommet""" pass def depiler(p:'Pile NON VIDE') -> 'Pile': """Renvoie une pile basée sur p mais en supprimant le sommet""" pass def lire_sommet(p:'Pile NON VIDE') -> 'Element': """Renvoie la valeur actuellement au sommet de la Pile p NON VIDE""" pass if __name__ == '__main__': a = nouvelle_pile() a = empiler(a, 0) a = empiler(a, 5) a = empiler(a, 10) a = empiler(a, 15) s = lire_sommet(a) assert s == 15 a = depiler(a) s = lire_sommet(a) assert s == 10

05° Estimer le coût des cinq fonctions primitives.

...CORRECTION...

On retrouve les mêmes coûts qu'avec la liste chaînée dont les cellules sont des tuples : toutes les opérations sont à coût CONSTANT sur la tête.

Et c'est pour cela qu'on a placer le sommet du côté de la tête.

(Bilan Correction) 2.2 Module de Pile implémentée via tuple

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
"""MODULE d'implémentation d'une PILE IMMUABLE avec comme modèle une liste chaînée de TUPLES La Pile vide sera représentée par un tuple vide (). Le sommet d'une Pile non vide est situé du même côté que la tête de la liste chaînée associée. Fonctions primitives -------------------- CST : nouvelle_pile() -> 'Pile VIDE' CST : est_pile_vide(p:'Pile') -> bool CST : empiler(p:'Pile', elt:'Element') -> 'Pile NON VIDE' CST : depiler(p:'Pile NON VIDE') -> 'Pile' CST : lire_sommet(p:'Pile NON VIDE') -> 'Element' """ def nouvelle_pile() -> 'Pile VIDE': """Renvoie une nouvelle Pile vide""" return () def est_pile_vide(p:'Pile') -> bool: """Prédicat qui renvoie True si p est une Pile vide""" return p == () def empiler(p:'Pile', elt:'Element' ) -> 'Pile NON VIDE': """Renvoie une pile basée sur p mais en rajoutant elt au sommet""" return (elt, p) # renvoie un tuple contenant elt et menant ensuite à la pile p reçue def depiler(p:'Pile NON VIDE') -> 'Pile': """Renvoie une pile basée sur p mais en supprimant le sommet""" return p[1] # renvoie un tuple qui est le reste de la pile p def lire_sommet(p:'Pile NON VIDE') -> 'Element': """Renvoie la valeur actuellement au sommet de la Pile p NON VIDE""" return p[0] # renvoie la valeur stockée dans la cellule Sommet if __name__ == '__main__': a = nouvelle_pile() a = empiler(a, 0) a = empiler(a, 5) a = empiler(a, 10) a = empiler(a, 15) s = lire_sommet(a) assert s == 15 a = depiler(a) s = lire_sommet(a) assert s == 10

Attention : IMMUABLE, la pile n'est pas vraiment modifiée. Pour modifier une pile, il faut donc écrire
a = empiler(a, 15)

06° Utiliser cette implémentation pour constater qu'on a bien une Pile et, qu'à part l'immuabilité, on ne peut pas savoir comment tout cela est implémenté à l'interne tant qu'on utilise uniquement les fonctions d'interface.

>>> a = nouvelle_pile() >>> a = empiler(a, 5) >>> a (5, ()) >>> a = empiler(a, 10) >>> a (10, (5, ())) >>> a = empiler(a, 20) >>> a (20, (10, (5, ()))) >>> a = depiler(a) (10, (5, ())) >>> a (10, (5, ())) >>> depiler(a) (5, ()) >>> a (10, (5, ())) # on voit que a n'a pas été modifié car on a juste écrit depiler(a) >>> a = empiler(a, 40) >>> a (40, (10, (5, ()))) >>> a = depiler(a) (10, (5, ())) >>> a (10, (5, ())) # on voit que a a été modifié car on a écrit a = depiler(a) >>> a = depiler(a) (5, ())

3 - Implémentation d'une Pile MUTABLE avec un modèle de liste chaînée objet

3.1 Schéma de principe avec une liste chaînée Python

Pour obtenir des primitives à coût CONSTANT, il faut placer le SOMMET de notre Pile du côté de la Tête de la liste chainée.

En effet, les fonctions inserer_tete() et supprimer_tete() sont à coût constant.

Chaque cellule est un objet comportant deux attributs :

  1. v pour la valeur
  2. s pour le successeur qui peut être une Cellule ou None.
graph LR F(("ENTREE")) -.- empilement -.- A("Sommet de Pile
Tête de Liste") --> B("Cellule 1") --> C("Cellle 2") -.-> D("Cellule n-2") ---> E("Cellule n-1 de Fin") I(("SORTIE")) -.- dépilement -.- A style E fill:#99f,stroke:#333,stroke-width:2px style A fill:#ff9,stroke:#333,stroke-width:2px style B fill:#99f,stroke:#333,stroke-width:2px style C fill:#99f,stroke:#333,stroke-width:2px style D fill:#99f,stroke:#333,stroke-width:2px

07° Compléter les différentes primitives pour parvenir à implémenter notre Pile MUTABLE sous forme d'objets à l'interne.

Commencez par réaliser les méthodes de l'objet puis compléter les fonctions pour qu'un utilisateur utilisant vos fonctions ne se rende pas compte qu'on utilise des objets pour implémenter la Pile.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
"""MODULE d'implémentation d'une Pile sous forme d'une liste Chaînée MUTABLE avec un modèle de liste chaînée OBJET à l'interne où la Tete de la liste correspond au Sommet de la Pile. On peut manipuler directement l'objet Pile via les méthodes. On peut utiliser les fonctions sans se rendre compte qu'on manipule un objet. Méthodes primitives de la classe Pile ------------------------------------- CST : Pile() -> 'Pile VIDE' CST : Pile.est_pile_vide(p:'Pile') -> bool CST : Pile.empiler(p:'Pile', elt:'Element') -> None CST : Pile.depiler(p:'Pile NON VIDE') -> 'Element' CST : Pile.lire_sommet(p:'Pile NON VIDE') -> 'Element' Fonctions primitives de la Pile ------------------------------- CST : Pile() -> 'Pile VIDE' CST : Pile.est_pile_vide(p:'Pile') -> bool CST : Pile.empiler(p:'Pile', elt:'Element') -> None CST : Pile.depiler(p:'Pile NON VIDE') -> 'Element' CST : Pile.lire_sommet(p:'Pile NON VIDE') -> 'Element' """ # Déclaration des classes Cellule et Pile class Cellule: def __init__(self, valeur:'Element', successeur:'Cellule|None'): self.v = valeur # Valeur ne peut pas être None si liste homogène self.s = successeur # successeur vaut None si Cellule de Fin class Pile: def __init__(self): self.sommet = None def est_pile_vide(self:'Pile') -> bool: pass def empiler(self:'Pile', elt:'Element') -> None: pass def depiler(self:'Pile NON VIDE') -> 'Element': pass def lire_sommet(self:'Pile NON VIDE') -> 'Element': """Renvoie la valeur au sommet de la Pile""" pass # Déclaration des fonctions primitives si on veut "cacher" l'objet def nouvelle_pile() -> 'Pile VIDE': pass def est_pile_vide(p:'Pile') -> bool: """Prédicat qui renvoie True si p est une Pile vide""" pass def empiler(p:'Pile', elt:'Element' ) -> None: """Rajoutant elt au sommet de la pile p""" pass def depiler(p:'Pile NON VIDE') -> 'Element': """Dépile et renvoie le sommet de la pile p""" pass def lire_sommet(p:'Pile NON VIDE') -> 'Element': """Renvoie la valeur actuellement au sommet de la Pile p NON VIDE""" pass # Programme-test if __name__ == '__main__': p = nouvelle_pile() empiler(p, 0) empiler(p, 5) empiler(p, 10) assert lire_sommet(p) == 10 depiler(p) assert lire_sommet(p) == 5

08° Estimer le coût des cinq fonctions primitives.

...CORRECTION...

On retrouve les mêmes coûts qu'avec la liste chaînée dont les cellules sont des tuples : toutes les opérations sont à coût CONSTANT sur la tête.

Et c'est pour cela qu'on a placer le sommet du côté de la tête.

(Bilan Correction) 3.2 Module de Pile implémentée avec un modèle liste chaînée objet

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
"""MODULE d'implémentation d'une Pile MUTABLE avec comme modèle une liste chaînée d'objets où la Tete de la liste correspond au Sommet de la Pile. On peut manipuler directement l'objet Pile via les méthodes. On peut utiliser les fonctions sans se rendre compte qu'on manipule un objet. Méthodes primitives de la classe Pile ------------------------------------- CST : Pile() -> 'Pile VIDE' CST : Pile.est_pile_vide(p:'Pile') -> bool CST : Pile.empiler(p:'Pile', elt:'Element') -> None CST : Pile.depiler(p:'Pile NON VIDE') -> 'Element' CST : Pile.lire_sommet(p:'Pile NON VIDE') -> 'Element' Fonctions primitives de la Pile ------------------------------- CST : nouvelle_pile() -> 'Pile VIDE' CST : est_pile_vide(p:'Pile') -> bool CST : empiler(p:'Pile', elt:'Element') -> None CST : depiler(p:'Pile NON VIDE') -> 'Element' CST : lire_sommet(p:'Pile NON VIDE') -> 'Element' """ # Déclaration des classes Cellule et Pile class Cellule: def __init__(self, valeur:'Element', successeur:'Cellule|None'): self.v = valeur # Valeur ne peut pas être None si liste homogène self.s = successeur # successeur vaut None si Cellule de Fin class Pile: def __init__(self): self.sommet = None def est_pile_vide(self:'Pile') -> bool: return self.sommet is None def empiler(self:'Pile', elt:'Element') -> None: """Rajoute une nouvelle cellule contenant elt au sommet de la Pile""" nouvelle = Cellule(elt, None) # Etape 1 : création de la cellule nouvelle.s = self.sommet # Etape 2 : on relie la nouvelle cellule à l'"ancien" sommet self.sommet = nouvelle # Etape 3 : modification de la liste def depiler(self:'Pile NON VIDE') -> 'Element': """Supprime la cellule de tête et renvoie l'élément qui y était stocké""" ancienne_valeur = self.sommet.v # Etape 1 : on mémorise la valeur au sommet self.sommet = self.sommet.s # Etape 2 : le sommet devient le successeur du sommet supprimé return ancienne_valeur # Etape 3 : on renvoie la valeur stockée def lire_sommet(self:'Pile NON VIDE') -> 'Element': """Renvoie la valeur au sommet de la Pile""" return self.sommet.v # Déclaration des fonctions primitives si on veut "cacher" l'objet def nouvelle_pile() -> 'Pile VIDE': """Renvoie une nouvelle Pile vide""" return Pile() def est_pile_vide(p:'Pile') -> bool: """Prédicat qui renvoie True si p est une Pile vide""" return p.est_pile_vide() def empiler(p:'Pile', elt:'Element' ) -> None: """Rajoutant elt au sommet de la pile p""" p.empiler(elt) def depiler(p:'Pile NON VIDE') -> 'Element': """Dépile et renvoie le sommet de la pile p""" return p.depiler() def lire_sommet(p:'Pile NON VIDE') -> 'Element': """Renvoie la valeur actuellement au sommet de la Pile p NON VIDE""" return p.lire_sommet() # Programme-test if __name__ == '__main__': p = nouvelle_pile() empiler(p, 0) empiler(p, 5) empiler(p, 10) assert lire_sommet(p) == 10 depiler(p) assert lire_sommet(p) == 5

4 - Mini-Projet : Pile sous forme dictionnaire + tableau statique

On veut créer une implémentation de Pile mutable ne pouvant contenir que 10 élements.

Le principe est le suivant :

  • On crée un dictionnaire qui encodera la pile et qui intégre
    • le nombre d'éléments dans la Pile via une clé "nbr",
    • le nombre d'éléments maximum dans la Pile via une clé "max",
    • les données de la Pile via une clé "data"
  • On crée un tableau statique contenant 10 cases vides. Ce tableau est destiné à contenir les données de notre Pile et sa référence est la valeur associée à la clé "data".

Un exemple d'utilisation qui devra fonctionner de la même manière avec votre implémentation une fois qu'elle sera fonctionnelle.

>>> a = nouvelle_pile() >>> empiler(5, a) >>> a {'nb': 1, 'max': 10, 'data': [5, None, None, None, None, None, None, None, None, None]} >>> empiler(10, a) >>> a {'nb': 2, 'max': 10, 'data': [5, 10, None, None, None, None, None, None, None, None]} >>> empiler(20, a) >>> a {'nb': 3, 'max': 10, 'data': [5, 10, 20, None, None, None, None, None, None, None]} >>> est_pile_vide(a) False >>> est_pile_pleine(a) False >>> taille(a) 3 >>> depiler(a) 20 >>> a {'nb': 2, 'max': 10, 'data': [5, 10, None, None, None, None, None, None, None, None]} >>> empiler(40, a) >>> a {'nb': 3, 'max': 10, 'data': [5, 10, 40, None, None, None, None, None, None, None]} >>> depiler(a) 40 >>> depiler(a) 10 >>> depiler(a) 5 >>> depiler(a) >>> a {'nb': -1, 'max': 10, 'data': [None, None, None, None, None, None, None, None, None, None]}

Remarquez bien que comme l'utilisateur a dépilé une pile vide, le compteur final donne quelque chose d'aberrant : la taille de la pile vaudrait -1 éléments !

Et voici le début de l'implémentation. A vous de la finir !

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
"""Implémentation d'une PILE MUTABLE avec un modèle dictionnaire et tableau statique""" # Phase d'importation # Phase de déclaration des CONSTANTES et variables globales TAILLE = 10 # Phase de déclaration des classes et fonctions def nouvelle_pile() -> 'Pile': """Renvoie la référence d'une nouvelle Pile de TAILLE limitée""" p = {} p['nb'] = 0 # contient le nombre de données stockées p['max'] = TAILLE p['data'] = [None for x in range(TAILLE)] # Aucune donnée au début return p def est_pile_vide(p:'Pile') -> bool: """Prédicat qui renvoie True si la Pile est bien vide""" return p['nb'] == 0 def est_pile_pleine(p:'Pile') -> bool: """Prédicat qui renvoie True si la Pile est pleine""" pass def empiler(elt:'Elt', p:'Pile NON PLEINE') -> None: """Empile l'élément dans une pile NON PLEINE""" pass def depiler(p:'Pile NON VIDE') -> 'Elt': """Dépile et renvoie le sommet d'une pile NON VIDE""" pass def lire_sommet(p:'Pile NON VIDE') -> 'Elt': """Renvoie la donnée stockée au sommet de la pile NON VIDE p""" pass def taille(p:'Pile') -> int: pass def tester(): """Fonction ne servant qu'à valider en partie vos fonctions d'interface""" print("DEBUT DU JEU DE TEST...") a = nouvelle_pile() assert a == {'nb': 0, 'max': 10, 'data': [None, None, None, None, None, None, None, None, None, None]} print("... Création : ok") empiler(5, a) assert a == {'nb': 1, 'max': 10, 'data': [5, None, None, None, None, None, None, None, None, None]} empiler(10, a) assert a == {'nb': 2, 'max': 10, 'data': [5, 10, None, None, None, None, None, None, None, None]} empiler(20, a) assert a == {'nb': 3, 'max': 10, 'data': [5, 10, 20, None, None, None, None, None, None, None]} print("... Empilement : ok") assert est_pile_vide(a) == False assert est_pile_pleine(a) == False assert taille(a) == 3 assert depiler(a) == 20 assert a == {'nb': 2, 'max': 10, 'data': [5, 10, None, None, None, None, None, None, None, None]} empiler(40, a) assert a == {'nb': 3, 'max': 10, 'data': [5, 10, 40, None, None, None, None, None, None, None]} assert depiler(a) == 40 assert depiler(a) == 10 assert depiler(a) == 5 assert a == {'nb': 0, 'max': 10, 'data': [None, None, None, None, None, None, None, None, None, None]} print("... Dépilement : ok") print("FIN DU JEU DE TESTS.") # --- Programme principal --- if __name__ == '__main__': import doctest DEBUG = False # Passer la constante à True pour activer le jeu de tests via assertions if DEBUG: tester() doctest.testmod() # Pour utiliser la documentation comme jeu de tests

✎ 09° Voici la notation :

  1. Implémentation correcte : 8 points
  2. Utiliser autant que possible les primitives déjà réalisées pour réaliser de nouvelles fonctions : 2 points
  3. Justification du type de coût en lecture, en insertion et en suppression : 2 points
  4. Création d'une fonction swap() qui inverse le sommet et l'élément suivant pour une pile d'au moins 2 éléments : 2 points
  5. Création d'une fonction vider() qui vide la pile NON VIDE : 2 points
  6. Documentation des différentes fonctions d'interface : 2 points
  7. Réalisation d'assertions pour vérifier que les piles à gérer ne sont pas vides avant lecture ou suppression, ni pleine en cas d'insertion, que la pile contient bien au moins deux éléments avant swap... : 2 points.

La prochaine fois, on implémentera les Files.

Activité publiée le 14 10 2020
Dernière modification : 16 10 2021
Auteur : ows. h.