Vous êtes sur la page 1sur 7

MP - Informatique Août 2022

Chapitre 0 : Quelques notions introductives

1 Piles et Files
Introduction :
L’une des problématiques les plus importantes de l’informatique de nos jours est le stockage de
données. Pour traiter efficacement ces dernières, il faut les ranger de manière adéquate, comme dans
la vie quotidienne. L’objet informatique qui stocke des valeurs s’appelle une structure de données, qui
est caractérisée par les opérations qu’elle permet et le coût de ces opérations. On peut par exemple,
souhaiter connaı̂tre le nombre d’éléments qu’elle contient, accéder à un élément donnée, en parcourir
tous les éléments... En MPSI, il vous a été présenté au moins deux structures de données : les tableaux
et les listes.

L’image associée à une pile est donc ”dernier arrivé, premier sorti”, ou en anglais ”last in, first out”
d’où l’abréviation LIFO.

Etant donné une pile, on peut :


- demander si la pile est vide
- prendre le premier élément de la pile
- déposer un élément sur la pile.

Ces opérations sont traditionnellement appelées push pour empiler et pop pour dépiler.
Exemples d’applications des structures de pile
La structure de pile est couramment utilisée en ingénérie inverse, comme pour ”le retour à la page
précédente” sur le net, ou encore les touches ”Undo” des éditeurs de textes, ou tout autre ”annuler
saisie, annuler copier...”

En ce qui concerne les files, elles sont utilisées dans la vie courante pour faire la queue devant un
guichet par exemple, (on parle de queue en anglais). Etant donné une file d’attente, on peut
- demander si la file est vide, i.e. s’il y a des personnes qui attendent
- prendre le client en tête de file pour traiter sa demande
- placer un client à la queue de la file pour qu’il attende son tour.

L’image associée est donc premier arrivé premier sorti, ou en anglais ”first in, firts out”, d’où
l’abréviation FIFO.

Ces opérations sont toujours appelées push ( pour ajouter un élément) et pop ( pour enlever un
élément).

1.1 Piles
Définition 1. Les piles sont des structures de données mutables sur lesquelles on peut effectuer les
opérations suivantes :
- tester si la pile est vide
- empiler : ajouter un élément au sommet de la pile
- dépiler : extraire et renvoyer l’élément du sommet de la pile
Exemple 1. En informatique, les piles peuvent être utilisées pour stocker les appels successifs lors
de l’évaluation des fonctions récursive. On parle de pile d’exécution du programme.
Remarque :
En général, les piles ont une taille maximale fixée par le langage. Par exemple, le nimbre maximal
d’appels récursifs par défaut en Python est 1000. Lors de l’ajout d’un élément à une pile déjà pleine,
un message d’erreur est renvoyé : il s’agit d’un débordement de pile : ”stack overflow” en anglais).

En Python, les piles sont implémentées par des listes où seules mes opérations autorisées sont pop
et append. Nous illustrons ci-dessous la construction puis la manipulation d’une pile en insistant
sur son caractère mutable.

>>> pile=[]
>>> pile.append(1)
>>> pile.append(10)
>>> pile.append(2)
>>> print(pile)
[1, 10, 2]
>>> a=pile.pop()
>>> print(a)
2
>>> print(pile)
[1, 10]
>>> pile==[]
False >>> b=pile.pop()
>>> print(pile)
[1]
>>> pile==[]
False
>>> c=pile.pop()
>>> print(pile)
[]
>>> pile==[]
True
>>> pile.pop() Traceback (most recent call last)
File ”<stdin>”, line 1, in <module>
IndexError : pop from empty list

Remarque :
D’après les spécifications de Python sur les listes, ces opérations sont bien de complexité constante.

1.2 Files
Définition 2. Les files sont des structures de données mutables sur lesquelles on peut effectuer les
opérations suivantes :
- tester si la file est vide
- ajouter une élément à la queue de la file
- extraire en renvoyer l’élément du sommet de la file.

Exemple 2. En informatique, les files sont utilisées pour gérer les instructions que doit effectuer
un processeur. Les instructions sont stockées sous forme de file dans des buffers d’entrée/sorte en
attente de traitement par le processeur.

En Python, les files sont implémentées dans le module deque. Les opérations sont popleft et ap-
pend. Nous illustrons ci-dessous la manipulation d’une file en insistant sur son côté mutable.
>>> from collections import deque
>>> file=deque([])
>>> file.append(’Ada’)
>>> file.append(’Grace’)
>>> file.append(’Alan’)
>>> print(file)
deque([’Ada’,’Grace’,’Alan’])
>>> premiere=file.popleft()
>>> print(premiere)
Ada
>>> print(file)
deque([’Grace’,’Alan’])
>>> file==deque([])
False
>>> seconde=file.popleft()
>>> print(file)
deque([’Alan’])
>>> file==deque([])
False
>>> troisieme=file.popleft()
>>> print(file)
[]
>>> file==[]
True
>>> file.popleft() Traceback (most recent call last)
File ”<stdin>”, line 1, in <module>
IndexError : pop from empty deque

1.3 Deux applications amusantes des piles


1.3.1 Notation polonaise inversée
On souhaite réaliser un programme pour évaluer des expressions arithmétiques en notation polonaise
inversée (NPI), comme dans certaines calculatrices. Dans cette notation, les opérateurs arithmétiques
sont placés après leurs opérandes, en notation post-fixée. Ainsi, l’expression 2 + 3 s’écrit 2 3+ et l’ex-
pressions 2 + 3 ∗ 4 devient 2 3 4 ∗ +. L’intérêt de cette notation est qu’elle rend l’utilisation des
parenthèses inutiles. Ainsi, l’expression (2 + 3) ∗ 4 s’écrit simplement 2 3 + 4 ∗.
Par la suite, les expressions arithmétiques en NPI sont représentées par des tableaux contenant des
entiers et des caractères. Par exemple, 1 2 + 3 ∗ correspond au tableau [1, 2,0 +0 , 3,0 ∗0 ].
L’évaluation d’une expression en NPI nécessite une pile. L’idée consiste à parcourir le tableau de
gauche vers la droite et à empiler chaque nombre rencontré. Lorsque l’élément courant est un
opérateur, on dépile les deux opérandes, on effectue le calcul et on empile le résultat.
La solution que l’on propose ici fait l’hypothèse que les expressions arithmétiques contiennent uni-
quement les symboles + et ∗. Il est immédiat de l’étendre à d’autres opérateurs.

On commence par créer une pile p.

On parcourt alors tous les éléments du tableau exp, de la gauche vers la droite. Si l’élément courant
c est un opérateur arithmétiques (caractères + et ∗), on dépile les deux opérandes x et y de p et on
empile x + y ou x ∗ y selon la valeur de c.

Sinon, c’est un nombre et on l’empile dans p.


Quand on sort de la boucle for, il ne reste plus qu’à dépiler la valeur finale v de l’expression et
à vérifier que la pile p est bien vide.

1.3.2 Analyse des mots bien parenthésés


Comme deuxième application de piles, on considère le problème suivant : étant données une chaı̂ne de
caractères ne contenant que des caractères 0 (0 et 0 )0 , déterminer s’il s’agit d’un mot bien parenthésé.
Un mot bien parenthèse est soit le mot vide, soit la concaténation de deux mots bien parenthèses,
soit un mot parenthèse mis entre parenthèses. Ainsi, les trois mots 00 , 0 ()()0 et 0 (())()0 sont bien pa-
renthésés. A l’inverse, les mots 0 (()0 , ())0 ou encore 0 )0 ne le sont pas. On se propose se plus d’indiquer,
pour chaque parenthèse ouvrante, la position de la parenthèse fermante correspondante. Ainsi, pour
le mot 0 (())()0 on donnera les couples d’indices (0, 3), (1, 2) et (4, 5).
L’idée consiste à parcourir le mot de la gauche vers la droite et à utiliser une pile pour indiquer les
indices de toutes les parenthèses ouvertes, et non encore fermées, vues jusqu’à présent.

On commence donc par créer une telle pile p :

On parcourt alors tous les caractères du mot, de la gauche vers la droite avec une boucle for.

Si le caractère est une parenthèse ouvrante, on emplie son indice i :

Sinon, c’est qu’il s’agit d’une parenthèse fermante. Si la pile est vide, c’est que le mot n’est pas
bien parenthèse, car on vient de trouver une parenthèse fermante à laquelle ne correspond aucune
parenthèse ouvrante. On le signale immédiatement en renvoyant False.

Sinon, on dépile l’indice j de la dernière parenthèse ouvrante rencontrée et on affiche le couple


(j, i) pour signifier que la parenthèse ouvrante j correspond à la parenthèse fermante à l’indice i.

On remarque ici en quoi la structure de pile est pertinente. Elle permet ici de faire correspondre
chaque parenthèse fermante à la parenthèse ouvrante la plus proche, c’est-à-dire celle qui est au
somme de la pile. Quand on sort de la boucle for, il ne reste qu’à vérifier que la pile est bien vide.

En effet, le mot pourrait contenir plus de parenthèses ouvrantes que de parenthèses fermantes, et il
faut alors signaler que le mot est mal parenthésé.

2 Programmation objet
Le principe de programmation orientée objet est de créer des ensembles informatiques particuliers
auxquels on pourra appliquer des fonctions dédiées. Ces ensembles sont appelées de classes, les
éléments de ces ensembles sont appelés les objets, et les fonctions s’appliquant à des objets sont
appelées des méthodes.
Dans le cas de Python, vous avez déjà rencontré des méthodes s’appliquant à certains objets. Par
exemple, vous avez déjà vu l’utilité de la méthode sort qui s’applique aux objets de la classe list et
qui permet de trier une liste.
Dans ce chapitre, nous verrons comment créer nos propres classes d’objets et les méthodes qui pour-
ront leur être appliquées.
2.1 Les classes
Il est très simple de créer une classe d’objets. Voici un exemple.
i) class Maclasse( object ) :
ii) pass
Le mot clé pass joue le rôle d’un bloc d’instructions qui n’a aucune action. Il est très utile au
développement d’un projet pour créer des fonctions ou des classes. Par convention, les noms de classe
commenceront par une majuscule.

Nous allons à présent construire une classe permettant de représenter des distances en kilomètres ou
en miles. On définit ensuite la classe des objets de type Distance, qui dispose de trois attributs. Ils
ont pou nom unites, unite et dist.

i) class Distance( object ) :


ii) unites = [’km’,’mile’]
iii) def init ( self, d, unite=’km’) :
iv) self.dist = d
v) if unite in self.unites :
vi) self.unite=unite
vii) else :
viii) raise TypeError

Le mot clé self fait référence à l’objet lui-même. La fonction init est appelée lors de la création
d’un objet appartenant à la classe distance qui permet de définir et d’initialiser tout objet de la
classe, la syntaxe est la suivante :

<nom de la variable > = < nom de classe > (paramètres de l’objet).

Par exemple, l’instruction d=Distance(10) crée une variable nommée d appartenant à la classe
Distance. Le paramètre correspondant au champ unité n’ayant pas été précisé, l’attribut unité est
initialisé à la valeur ’km’.
Dans le cas de la classe Distance, lors de la création d’un objet, on initialise les attributs unites,
unité et dist. Chaque attribut d’un objet est accessible avec une commande du type
< nom de l’objet >.< nom de l’attribut >.
Notez bien la présence du point entre le nom de l’objet et le nom de l’attribut.

Exemple 3. Dans les lignes de commande suivantes, on crée une variable de type Distance, puis
on accède à chacun de ses attributs.
>>> d = Distance(10)
>>> d.unites
[0 km0 ,0 mile0 ]
>>> d.unite
’km’
>>> d.dist
10

Pour modifier les attributs, il suffit d’utiliser une simple affectation.

Exemple 4. On reprend l’exemple précédent et la variable d représentant une distance, et modifions


son unité.
>>> d.unite=’mile’
>>> d.unite=
’mile’
>>> d.unite=’mille nautique’
>>> d.unite
’ mille nautique’
Dans cet exemple, on a changé l’unité de notre distance sans changer sa valeur numérique, ce qui
n’est pas très logique. De même, il est assez naturel que l’attribut unites ne soit pas modifiable par
un utilisateur.
Dans une classe donnée, on souhaiterait protéger certains attributs de l’objet. Pour cela, on peut
définir des attributs privés qui sont intrinsèquement liés à l’objet, mais qui ne sont pas directement
modifiables par l’utilisateur, sauf par l’appel d’une méthode. On parle d’encapsulation. Pour créer
des attributs privés, il suffit que le nom de l’attribut commence par deux caractères soulignés (un-
derscores).
On reprend l’exemple traité précédemment en rendant privés les attributs unites et unité, auxquels
l’utilisateur ne pourra plus accéder directement, contrairement à l’attribut dist qui garde son statut
de variable globale.

>>> class Distance( object ) :


>>> unites = [’km’,’mile’]
>>> def init ( self, d, unite=’km’) :
>>> self.dist = d
>>> if unite in self.unites :
>>> self. unite=unite
>>> else :
>>> raise TypeError

2.2 Les méthodes


Les fonctions attachés à une classe sont appelées des méthodes. Il s’agit de fonctions qui sont définies
dans le corps de la déclaration de la classe et qui ne peuvent être appelés que par des objets appar-
tenant à la classe.
Dans le cas de l’exemple précédent, l’attribut unité d’un objet de la classe Distance est privé,
et l’utilisateur ne peut y accéder directement. Pour donner accès à cette information, il faut créer
une méthode appelée unite qui admet un ensemble vide d’argument et qui retourne la valeur de
l’attribut unite de notre objet.

>>> class Distance( object ) :


>>> unites = [’km’,’mile’]
>>> def init ( self, d, unite=’km’) :
>>> self.dist = d
>>> if unite in self.unites :
>>> self. unite=unite
>>> else :
>>> raise TypeError
>>>
>>> def unite( self ) :
>>> return(self. unite)
On crée ci-dessous une variable d correspondant à une distance de 10 miles. On vérifie ensuite que
l’unité de d est bien le mile.
>>> d = Distance(10,’mile’)
>>> d.unite()
’mile’
Pour compléter cet exemple, nous allons écrire une méthode permettant de changer d’unité de lon-
gueur, appelée changeunite. Si l’unité n’est pas précisée, celle qui s’applique par défaut est le ki-
lomètre. Lors d’un changement d’unité, les attributs locaux km2mile et mile2km permettent
de recalculer la bonne distance.
Nous avons également besoin d’une méthode particulière nommée str , utilisée par la fonction str
pour convertir une objet de la classe Distance en chaı̂nes de caractères.
Le code vous a été fourni par mail. Pour le tester, on peut générer une variable d de type Distance,
égale à 1430 km. Les lignes ii et iii montrent le résultat du module str . A la ligne iv, on
applique le module changeunite à la distance d pour passer en miles, puis on affiche le résultat de
la transformation (v et vi).

>>> d = Distance(1430)
>>> print(”distance d = ” +str(d))
distance d = 1430 km
>>> d.changeUnite(’mile’)
>>> print(”distance d =” + str(d))
distance d=888.5608017 mile

Exercice 1.
1)
i) Définir une classe Intervalle permettant de représenter informatiquement les segments [a, b].
ii) Définir une méthode intersection permettant de calculer l’intersection de deux intervalles.
iii) Définir une méthode est vide qui retourne vrai si l’intervalle est vide, et faux sinon.
iv) Définir la méthode str pour un affichage adapté des intervalles.

2)
i) Définir une classe Temperature permettant de représenter informatiquement des températures
exprimées en Celsius, en Kelvin, ou en Fahrenheit.
ii) Définir la méthode str pour un affichage de la valeur de la température avec son unité.
iii) Définir une méthode convertir permettant le changement d’unité.

Vous aimerez peut-être aussi