Cette page complète les fiches déjà vues sur les piles, les files, les listes et les dictionnaires. Elle explique une autre façon de fabriquer une pile ou une file : avec un tableau de taille fixe et une position, plutôt qu'avec les méthodes toutes prêtes d'une liste Python. Elle explique aussi ce qu'est une interface (ou structure de données abstraite), et elle introduit la programmation orientée objet, avec l'exemple de la classe Point.
src à côté de cette page. Après avoir cliqué sur un lien pour le télécharger, on l'exécute dans un terminal, dans le dossier où il a été enregistré, avec python3 nom_du_fichier.py (Python doit être installé sur l'ordinateur). Chaque fichier affiche « OK » pour chaque exercice s'il n'y a pas d'erreur.
Chaque mot ci-dessous est utilisé exactement dans ce sens dans toute la page.
| Mot | Définition précise |
|---|---|
| Structure de données abstraite (ou interface) | Une structure définie uniquement par l'ensemble de ses valeurs possibles et la liste de ses opérations primitives, sans imposer une seule façon de la stocker en mémoire. |
| Implémentation | Une façon concrète de réaliser une structure de données abstraite en mémoire. Une même interface (par exemple une file) peut avoir plusieurs implémentations différentes. |
| Précondition | Une condition qui doit être vraie avant d'exécuter une opération pour qu'elle ait un sens (par exemple : « la file n'est pas vide » avant de retirer un élément). |
Assertion (assert) | Une instruction Python qui vérifie qu'une précondition est vraie, et qui arrête immédiatement le programme avec un message d'erreur si elle est fausse. |
| Tableau circulaire | Un tableau de taille fixe dans lequel, quand on atteint la dernière case, on reprend au début : la case suivant l'indice LEN - 1 est la case d'indice 0. |
Modulo (%) | Le reste d'une division entière. En Python, (i + 1) % LEN calcule la case suivant l'indice i dans un tableau circulaire de taille LEN : quand i vaut LEN - 1, le résultat revient à 0. |
La fiche Les files montre une file simplifiée, construite avec une liste Python (append et pop(0)). Cette section montre une autre implémentation, plus proche de la mémoire d'un ordinateur : un tableau de taille fixe, une position de tête, et un nombre d'éléments.
LEN_FILE est représentée par trois informations : un tableau de LEN_FILE cases, la position de la tête (l'indice du premier élément), et le nombre d'éléments actuellement dans la file. Le tableau est circulaire : quand on atteint la dernière case, on continue à la case 0.
On représente l'état d'une file d'attente à un bureau de vote, avec LEN_FILE = 10. Au départ, quatre électeurs (e1, e2, e3, e4) sont déjà dans la file : e3 et e4 sont aux cases 0 et 1, e1 et e2 sont aux cases 8 et 9, la tête est en position 8 (c'est e1 qui sera servi en premier).
| Opération | Rôle |
|---|---|
creer_file() | construit une file vide |
est_vide_file(file) | renvoie True si nombre_element vaut 0 |
premier_file(file) | renvoie l'élément en tête, sans le retirer |
ajouter_file(file, element) | ajoute un élément à la queue de la file |
retirer_file(file) | retire et renvoie la file mise à jour après le départ de l'élément en tête |
LEN_FILE = 10
def creer_file():
"""Construit une file vide, de taille fixe LEN_FILE."""
return {"tableau": ['_'] * LEN_FILE,
"position_tete": 0,
"nombre_element": 0}
def est_vide_file(file):
"""Renvoie True si la file ne contient aucun élément."""
return file["nombre_element"] == 0
def premier_file(file):
"""Renvoie l'élément en tête de file, sans le retirer."""
assert not(est_vide_file(file)), 'la file est vide'
pos = file["position_tete"]
return file["tableau"][pos]
def retirer_file(file):
"""Retourne la file mise à jour après la sortie de l'élément en tête."""
assert not(est_vide_file(file)), 'la file est vide'
pos = file["position_tete"]
file["tableau"][pos] = '_'
file["nombre_element"] -= 1
file["position_tete"] = (file["position_tete"] + 1) % LEN_FILE
return file
def ajouter_file(file, element):
"""Retourne la file mise à jour après l'ajout d'un élément en queue."""
assert file["nombre_element"] < LEN_FILE, 'la file est pleine'
pos = file["position_tete"]
nb_element = file["nombre_element"]
fin = (pos + nb_element) % LEN_FILE
file["tableau"][fin] = element
file["nombre_element"] += 1
return file
(position_tete + nombre_element) % LEN_FILE. Le % LEN_FILE fait « revenir » l'indice au début du tableau dès qu'il dépasserait la dernière case, exactement comme les aiguilles d'une horloge reviennent à midi après minuit.
creer_file, est_vide_file, premier_file, ajouter_file, retirer_file) sont toutes en O(1) : elles ne font jamais de boucle, seulement des calculs de position.file.pop(0) coûte O(n) car Python doit décaler tous les éléments restants. Ici, il n'y a jamais de décalage : seule la position de la tête change.De la même façon, une pile peut être implémentée avec un tableau de taille fixe LEN_PILE et un entier indiquant la position du sommet. Par convention, une position de sommet égale à -1 signifie que la pile est vide.
On reprend l'exemple d'une pile d'assiettes a1, a2, a3, a4, a5, déjà empilées dans cet ordre (a1 tout en bas, a5 au sommet).
| Opération | Rôle |
|---|---|
creer_pile() | construit une pile vide (position_sommet = -1) |
est_vide_pile(pile) | renvoie True si position_sommet == -1 |
sommet_pile(pile) | renvoie l'élément au sommet, sans le retirer |
empiler(pile, element) | ajoute un élément au sommet de la pile |
depiler(pile) | retire et renvoie la pile mise à jour après le départ de l'élément du sommet |
LEN_PILE = 10
def creer_pile():
"""Construit une pile vide, de taille fixe LEN_PILE."""
return {"tableau": ['_'] * LEN_PILE,
"position_sommet": -1}
def est_vide_pile(pile):
"""Renvoie True si la pile ne contient aucun élément."""
return pile["position_sommet"] == -1
def sommet_pile(pile):
"""Renvoie l'élément au sommet de la pile, sans le retirer."""
assert not(est_vide_pile(pile)), 'la pile est vide'
return pile["tableau"][pile["position_sommet"]]
def empiler(pile, element):
"""Retourne la pile mise à jour après l'empilement d'un élément au sommet."""
assert pile["position_sommet"] < LEN_PILE - 1, 'la pile est pleine'
pile["tableau"][pile["position_sommet"] + 1] = element
pile["position_sommet"] += 1
return pile
def depiler(pile):
"""Retourne la pile mise à jour après le dépilement de son sommet."""
assert not(est_vide_pile(pile)), 'la pile est vide'
pile["tableau"][pile["position_sommet"]] = '_'
pile["position_sommet"] -= 1
return pile
append et pop() : les deux implémentations d'une pile sont en O(1) pour toutes leurs opérations.| Opération | Rôle |
|---|---|
creer_liste() | renvoie la liste vide |
ajouter_en_tete(x, L) | insère l'élément x en tête de la liste L |
supprimer_en_tete(L) | supprime l'élément de tête de L |
est_vide(L) | renvoie True si L ne contient aucun élément |
tete(L) | renvoie l'élément de tête de L |
queue(L) | renvoie la liste L privée de son élément de tête |
On peut implémenter cette interface avec des tuples Python : la liste vide est représentée par None, et chaque élément ajouté « enveloppe » le reste de la liste dans un couple (valeur, reste_de_la_liste).
def creer_liste():
return None
def ajouter_en_tete(x, L):
return (x, L)
def tete(L):
return L[0]
def queue(L):
return L[1]
def est_vide(L):
return L == None
On construit la liste L2 = <'a' ; 'e' ; 'i' ; 'o' ; 'u' ; 'y' ; ∅> en ajoutant les voyelles en tête, en partant de la dernière voyelle de l'ordre alphabétique.
| Instruction | Valeur de la liste après l'instruction |
|---|---|
L2 = creer_liste() | None |
L2 = ajouter_en_tete('y', L2) | ('y', None) |
L2 = ajouter_en_tete('u', L2) | ('u', ('y', None)) |
L2 = ajouter_en_tete('o', L2) | ('o', ('u', ('y', None))) |
L2 = ajouter_en_tete('i', L2) | ('i', ('o', ('u', ('y', None)))) |
L2 = ajouter_en_tete('e', L2) | ('e', ('i', ('o', ('u', ('y', None))))) |
L2 = ajouter_en_tete('a', L2) | ('a', ('e', ('i', ('o', ('u', ('y', None)))))) |
Pour accéder à la voyelle 'i', il faut retirer deux fois la tête (avec queue), puis lire la nouvelle tête :
| Instruction | Résultat |
|---|---|
queue(L2) | ('e', ('i', ('o', ('u', ('y', None))))) |
queue(queue(L2)) | ('i', ('o', ('u', ('y', None)))) |
tete(queue(queue(L2))) | 'i' |
i appels à queue pour atteindre l'élément de rang i (en partant du rang 0 pour la tête). Contrairement à un tableau, il n'existe pas de raccourci : chaque élément intermédiaire doit être traversé un par un.
def nombre_element(L):
"""Nombre d'éléments d'une liste : 0 si vide, sinon 1 + le nombre
d'éléments de sa queue."""
if est_vide(L):
return 0
else:
return 1 + nombre_element(queue(L))
def val(i, L):
"""Élément de rang i (0 = tête), en avançant i fois avec queue."""
assert not(i < 0 or i > nombre_element(L)), 'index hors limite'
compteur = 0
temp_liste = L
while compteur < i:
temp_liste = queue(temp_liste)
compteur += 1
return tete(temp_liste)
def est_dans(x, L):
"""Vrai si x est un élément de la liste L, faux sinon."""
if est_vide(L):
return False
else:
if x == tete(L):
return True
else:
return est_dans(x, queue(L))
nombre_element(L) et est_dans(x, L) parcourent la liste élément par élément : dans le pire des cas, il faut autant d'étapes que d'éléments dans la liste, soit une complexité en O(n).val(i, L) demande exactement i étapes pour atteindre l'élément de rang i.list natif de Python (un tableau contigu) vu dans la fiche Les listes. Avec un vrai list Python, l[i] donne un accès direct en O(1) ; ici, il faut suivre queue pas à pas, ce qui coûte O(i) étapes.
| Mot | Définition précise |
|---|---|
| Classe | La description d'un ensemble d'objets ayant la même structure de données et les mêmes méthodes. |
| Objet (ou instance) | Une variable dont le type est une classe. On dit qu'un objet est une instance de sa classe. |
| Attribut | Une caractéristique (une donnée) associée à un objet, encapsulée à l'intérieur de celui-ci. |
| Méthode | Une fonction associée à une classe, qui agit sur les attributs d'un objet de cette classe. |
Constructeur (__init__) | Une méthode spéciale, obligatoire, appelée automatiquement lors de la création d'un objet, pour initialiser ses attributs. |
self | Dans une méthode, désigne l'objet lui-même sur lequel la méthode est appelée. |
| Notation pointée | La syntaxe objet.methode(arguments) utilisée pour appeler une méthode sur un objet. |
__repr__ | Une méthode spéciale qui définit la chaîne de caractères affichée quand on demande à voir un objet (avec print, ou en tapant son nom dans la console). |
PointOn définit une classe Point, pour manipuler des points dans un repère : deux attributs, abscisse et ordonnée, et une méthode deplace pour effectuer une translation.
class Point:
"Un point du plan muni d'un repère"
# Méthode constructeur
def __init__(self, x, y, col="#000000"):
self.abscisse = x
self.ordonne = y
self.couleur = col # couleur au format RVB, ex. "#FF00AA"
# Méthode deplace : translate le point de (dx, dy)
def deplace(self, dx, dy):
self.abscisse += dx
self.ordonne += dy
# Méthode rotation180 : symétrique du point par rapport à l'origine
def rotation180(self):
self.abscisse, self.ordonne = -self.abscisse, -self.ordonne
# Méthode pour représenter le point à l'affichage
def __repr__(self):
return f'({self.abscisse!r} ; {self.ordonne!r}) couleur : {self.couleur}'
Point ci-dessus, ainsi que les tests des Exercices 6 et 7.
__init__ et __repr__ sont des méthodes « double underscore » : des noms réservés que Python appelle automatiquement (__init__ à la création d'un objet, __repr__ à son affichage). On ne les appelle jamais soi-même avec la notation pointée.
La fiche Les dictionnaires présente la syntaxe Python (parcours, get, items...). Cette section présente l'interface abstraite à l'origine des dictionnaires : ce que le dictionnaire doit faire, indépendamment de la façon dont Python l'implémente.
| Opération | Rôle |
|---|---|
| ajout | associer une nouvelle valeur à une nouvelle clé |
| modif | remplacer la valeur associée à une clé déjà existante, sans changer la clé |
| suppr | supprimer une clé, et donc la valeur qui lui est associée |
| rech | retrouver la valeur associée à une clé |
| Recherche dans une liste | Recherche dans un dictionnaire | |
|---|---|---|
| Méthode | parcourir la liste, comparer chaque valeur | appliquer une fonction de hachage à la clé, pour obtenir directement une adresse mémoire |
| Nombre d'étapes, dans le pire cas | proportionnel au nombre d'éléments : O(n) | indépendant du nombre d'éléments : O(1) |
tete, queue, ajouter_en_tete), implémentable avec des tuples imbriqués ; les piles et les files en sont des cas particuliers.self désigne l'objet sur lequel la méthode est appelée, et __init__ l'initialise à sa création.