Les boucles, listes et fichiers
Introduction
Dans ce chapitre, nous présentons les éléments suivants :
-
Les boucles et les conditions imbriquées qui permettent de faire apparaître des structures complexes dans la chaîne de traitement.
-
Les listes qui permettent de stocker plusieurs informations et de retirer ou d’ajouter des informations dynamiquement.
-
La gestion des fichiers qui permet de lire et d’écrire des informations sur le disque.
L’imbrication
1. La notion de blocs d’imbrication
Le bloc de code associé à une instruction if et le corps de boucle d’une instruction for représentent la même notion que nous regroupons sous le terme de sous-bloc de code. Les sous-blocs ont la capacité de contenir d’autres sous-sous-blocs, les blocs peuvant ainsi s’imbriquer comme le font des poupées russes. Cependant, contrairement aux poupées russes, un bloc peut contenir plusieurs sous-blocs.
Dans le langage Python, un sous-bloc commence lorsque l’indentation se décale vers la droite et il se termine lorsque l’indentation revient au même niveau que celui du bloc supérieur.
Généralement, un sous-bloc est associé à une boucle ou à un branchement. Un bloc ne comportant aucun autre sous-bloc est appelé bloc terminal. Il est généralement composé d’instructions simples ne générant pas de boucle ou de branchement.
Analysons un exemple de code et faisons apparaître sa hiérarchie de blocs :
Le bloc principal contient trois instructions dont deux boucles for créant deux sous-blocs B et F de niveau 2 et un appel à la fonction print() à la dernière ligne. Les blocs D, E et G sont des blocs terminaux car ils ne contiennent aucun sous-bloc ; on remarque d’ailleurs qu’ils ne contiennent aucun branchement et aucune boucle. Le bloc B contient trois instructions dont une boucle for à l’origine du sous-bloc C. Le bloc C contient deux instructions if et else générant deux sous-blocs supplémentaires. Le bloc F contient trois instructions dont une boucle for à l’origine du sous-bloc G. Chacun de ces blocs contient autant de sous-blocs que de mots-clés if/else/for.
Lorsque l’on exécute en mode pas à pas des lignes de code appartenant à un même bloc, le temps semble s’arrêter pour les blocs supérieurs : les indices des boucles for des blocs supérieurs n’évoluent pas et les conditions associées aux instructions if des blocs supérieurs ont été validées.
Maintenant que nous savons identifier cette hiérarchie de blocs, nous allons examiner leur dynamique.
Lorsqu’une instruction if est rencontrée dans le bloc courant, alors :
-
Si sa condition est vraie :
-
on exécute entièrement son sous-bloc,
-
une fois le sous-bloc terminé, on passe à la ligne suivante dans le bloc courant, si elle existe. Dans le cas contraire, le bloc courant s’achève.
Lorsqu’une instruction for est rencontrée dans le bloc courant, alors :
-
Tant qu’il reste des éléments à traiter :
-
on entre dans le sous-bloc associé au corps de boucle,
-
on exécute entièrement ce sous-bloc,
-
lorsque la boucle s’achève, on passe à la ligne suivante dans le bloc courant, si elle existe. Dans le cas contraire, le bloc courant est terminé.
Vous remarquez que l’interaction de l’instruction if et de l’instruction for avec leurs sous-blocs et leur bloc courant est quasi identique. La seule différence repose sur le fait que la condition...
Les listes
Nous pourrions faire plusieurs pages d’explication sur les listes, mais c’est un objet très intuitif que nous préférons présenter par l’exemple.
Si l’on veut stocker l’ensemble des noms des élèves d’une classe, les températures du mois passé ou encore les plaques d’immatriculation d’un parc de véhicules de location, nous avons besoin d’une structure permettant de stocker plusieurs informations.
Il faut aussi que cette structure puisse évoluer dans le temps car un nouvel élève peut arriver, il faut alors ajouter son nom dans la liste. Un autre élève peut déménager, et dans ce cas, il faut retirer son nom de la liste. Les objets listes en Python offrent toutes ces fonctionnalités.
1. Créer une liste
Nous créons une liste vide en écrivant un symbole crochet ouvrant suivi d’un symbole crochet fermant. Pour créer une liste à partir d’un échantillon de valeurs, nous séparons ces différentes valeurs par des virgules et nous les encadrons par une paire de crochets. Voici un exemple :
>>> L = [] # Liste vide
>>> M = [ 11, 12, 15] # Liste avec trois éléments
>>> # la fonction print accepte les listes !
[11, 12, 15]
Sous Mac, les symboles crochets ne sont pas présents sur le clavier. Il faut utiliser une combinaison de touches : [Alt] [Maj] (.
2. Lire et modifier une liste
On peut lire ou modifier une valeur dans une liste en utilisant les symboles crochets [ ] contenant l’indice de l’élément considéré dans la liste. Attention, le premier élément d’une liste est positionné tout à gauche et correspond à l’élément d’indice 0. De ce fait, le dernier élément d’une liste de n éléments a pour indice n-1. La fonction clear() appliquée depuis une liste avec la syntaxe maListe.clear() permet de retirer l’ensemble des éléments.
Retenez ceci : le premier élément d’une liste se situe à l’indice 0.
Voici un exercice d’entraînement, essayez de trouver les résultats :
>>> L = [4,15,7]
>>> L
?
>>> L[2]
?
>>> L[0] += 1
>>> L
?
>>> L[2] += 9
>>> L
?
>>> L.clear()
>>> L
?
Voici l’affichage du programme :
[4, 15, 7]
7
[5, 15, 7]
[5, 15, 16]
[]
Pour désigner le dernier élément d’une liste, on peut écrire l’indice -1 qui correspond au premier élément de la liste en partant de la droite. L’indice -2 correspond au deuxième élément en partant de la droite, et ainsi de suite :
>>> L = [4,14,7]
>>> L[-1]
7 ...
La boucle while et les entrées-sorties fichier
1. La boucle while
Nous allons présenter la structure de boucle utilisant le mot-clé while. Comme pour la condition if, la boucle while nécessite une condition indiquant si son sous-bloc doit être réexécuté ou non. Tant que la condition est vraie, son sous-bloc est relancé. Prenons un exemple :
Ouverture d'un fichier texte
while ( la fin du fichier n'est pas atteinte ) :
Charger la prochaine ligne dans le fichier et l'afficher
Fermeture du fichier
Cette approche permet de parcourir l’intégralité d’un fichier et de traiter les informations qu’il contient. Comme le nombre de lignes à lire n’est pas connu au départ, on ne peut utiliser une boucle for avec indice pour traiter le fichier. Ainsi, la boucle while s’adapte très bien à ce scénario.
2. Ne pas remplacer une boucle for par une boucle while
On peut écrire le code suivant :
K = 10
while K < 13 :
print(K)
K += 1
print("Terminé")
Cette syntaxe est correcte. Par contre, elle est maladroite. Les trois éléments essentiels d’une boucle for sont présents : l’initialisation de l’indice de départ avec K=10, l’incrémentation présente en fin de bloc avec K += 1 et l’indice de fin venant de la condition K<13. Dans ce cas de figure, il faut préférer l’utilisation d’une boucle for et écrire :
for i in range(10,13):
print(K)
print("Terminé")
Pourquoi préférer l’utilisation d’une boucle for dans cette configuration ? Au final, les deux syntaxes sont correctes et les deux exemples produisent exactement le même résultat. La différence vient de la lisibilité. En effet, dans un programme comportant bien plus de lignes de code, l’incrémentation K+=1 peut être très éloignée de l’instruction while et plusieurs lignes de code peuvent se trouver entre l’affectation K=10 et l’instruction while. Dans cette configuration, les trois points essentiels d’une boucle : initialisation, incrémentation et condition, se retrouvent éloignés les uns des autres dans le code, ce qui compromet la lisibilité. La boucle for a cet avantage, non pas d’être plus rapide, mais de rassembler ces trois informations essentielles sur une même ligne, ce qui s’avère très judicieux pour la lisibilité.
3. Lire et écrire dans un fichier
Pour écrire sur le disque, la fonction incontournable est open(). Son premier argument indique le chemin du fichier sur le disque, et le deuxième le mode d’ouverture. Pour créer un fichier, vous avez le choix entre deux modes :
-
le mode "w" (abréviation du terme "write"), qui remet à zéro le contenu du fichier. Si le fichier n’existe pas, il est créé, et s’il existe, les informations présentes sont écrasées.
-
le mode "a" (abréviation du terme "append"), qui permet...
Les listes et chaînes de caractères
1. Conversion entre listes et chaînes
Nous rappelons que la méthode write(), servant à écrire des informations dans un fichier, n’accepte que des chaînes de caractères. Ainsi, elle ne traite pas de valeurs numériques, ce qui nous oblige, en cas de besoin, à les convertir en chaînes de caractères. Elle ne peut pas non plus gérer des structures plus complexes, comme les listes. Nous aimerions pouvoir écrire : f.write([4,5,6]), mais ce n’est pas possible. Pour cela, nous utilisons une solution clés en main avec la librairie json qui fournit des méthodes permettant de compacter des données quelconques dans une chaîne de caractères (les pros disent "sérialiser"). Pour utiliser les fonctions de la librairie, il faut d’abord l’importer en début de programme et ensuite utiliser la syntaxe json.mafonction(). Voici un exemple dont la source de cet exemple est disponible en téléchargement depuis l’onglet Compléments.
>>> import json
>>> L = [ 4, 6.0, "TOTO", ["SOLEIL", 99,-3.14]]
>>> L
[4, 6.0, 'TOTO', ['SOLEIL', 99, -3.14]]
>>> var = json.dumps(L)
>>> var
JSON : [4, 6.0, "TOTO", ["SOLEIL"...
Des astuces à connaître
Afin de faire des exercices d’entraînement plus variés, nous allons utiliser des opérateurs et des instructions nouvelles. Nous vous les présentons car, de toute façon, ils font partie des classiques de la programmation.
1. Les paramètres optionnels de la fonction print
Lorsque la fonction print() reçoit plusieurs arguments, par exemple print("un","deux"), elle les affiche en les séparant par un espace. Qu’elle reçoive zéro, un ou plusieurs arguments, cette fonction effectue un retour à la ligne.
Il est possible de modifier ce comportement en utilisant des paramètres optionnels :
-
Le paramètre sep permet de changer le séparateur inséré entre les arguments de la fonction print().
-
Le paramètre end permet de redéfinir le retour à la ligne par une nouvelle chaîne qui peut être éventuellement vide.
Voici un exemple :
>>> print("Hibou","Caillou")
Hibou Caillou
>>> print("Toto","Titi",sep="__")
Toto__Titi
Le paramètre end ne fonctionne pas en mode interactif. Il faut donc écrire un programme Python pour le tester :
print("1","2",end="_")
print("3","4")
>> 1 2_3 4
print("1","2",end="")
print("3","4")
>> 1 23 4
2. L’opérateur modulo %
L’expression a%b (a modulo b) entre deux entiers a et b retourne le reste de la division euclidienne de a par b. Ainsi, lorsqu’on divise 7 par 4, on obtient 1 et il reste 3. Cette valeur 3 correspond au résultat de l’opérateur modulo.
L’opérateur modulo peut s’avérer pratique dans certaines situations. Son utilisation est assez amusante : si l’on examine la suite i%7, on obtient une série de valeurs 0, 1, 2, 3, 4, 5 et 6 bouclant à l’infini :
0%7=0 |
1%7=1 |
2%7=2 |
3%7=3 |
4%7=4 |
5%7=5 |
6%7=6 |
7%7=0 |
8%7=1 |
9%7=2 |
10%7=3 |
11%7=4 |
12%7=5 |
13%7=6 |
14%7=0 |
15%7=1... |
Exercices d’application
1. Boucles for imbriquées I
Indiquez les valeurs affichées par le programme suivant :
for i in range(3) :
for j in range(7):
print(i+j,end="")
print()
Correction guidée :
Le bloc principal contient une boucle for i, cet indice va parcourir les valeurs 0, 1 et 2. La boucle for i définit un sous-bloc contenant une boucle for j et une instruction print(). Lorsque que nous entrons dans ce sous-bloc, la valeur de l’indice i est fixée. Par exemple, au premier passage elle vaut 0. La boucle for j effectue une série d’affichages pour j allant de 0 à 6. Comme le retour à la ligne de l’instruction print() est désactivé, les chiffres sont affichés les uns derrière les autres. Une fois la boucle for j terminée, nous passons à l’instruction suivante au même niveau, c’est-à-dire à l’instruction print() qui n’affiche rien, mais produit un retour à la ligne. Ainsi, nous obtenons :
>> 0123456
Après l’instruction print(), il n’y a plus d’instruction. Nous remontons donc à la boucle for i supérieure. L’indice i change alors pour la valeur 1 et le sous-bloc contenant la boucle for j est relancé avec cette nouvelle valeur. La mécanique à l’intérieur de ce bloc reste la même, seule change la valeur affichée, car cette fois i vaut 1. Nous obtenons ainsi l’affichage suivant :
>> 123457
Une fois le sous-bloc terminé, nous remontons à la boucle for i pour passer à la dernière valeur à étudier : 2. Le sous-bloc est relancé avec cette valeur et produit l’affichage suivant :
>> 234578
Une fois le sous-bloc terminé, nous remontons à la boucle for i. Comme il n’y a plus d’indice à parcourir, la boucle for i est terminée. Nous passons à l’instruction suivante au même niveau. Comme il n’y en a pas, le programme est terminé.
Voici le résultat final :
>> 0123456
>> 1234567
>> 2345678
2. Boucles for imbriquées II
Indiquez les valeurs affichées par le programme suivant :
for i in range(1,7) :
for j in range(i):
print("#",end="")
print()
Correction guidée :
La structure est proche de l’exercice précédent. Cependant, la plage de valeurs de la deuxième boucle dépend de l’indice de la première boucle, ce qui complique un peu les choses.
Au premier passage dans le sous-bloc, la valeur de i est fixée à 1. Ainsi, la boucle for j lance l’instruction print("#") une seule fois, et après cela, un retour à la ligne est effectué grâce à la fonction print().
Au deuxième passage dans le sous-bloc, la valeur de i est fixée à 2. La boucle for j affiche donc deux fois le caractère # puis l’instruction print() retourne à la ligne.
Ainsi de suite, chaque fois que l’on passe dans le sous-bloc, on affiche i fois le caractère...
Exercices d’entraînement
La correction de cet exercice est disponible en téléchargement depuis l’onglet Compléments. Nous tentons de reprogrammer la fonction native sum() permettant de calculer la somme des éléments d’une liste. Pour effectuer ce calcul, il faut utiliser une variable supplémentaire qui additionne les valeurs dans la liste lors du parcours. Pensez à initialiser cette variable correctement. La liste est de taille quelconque, tenez-en compte dans l’écriture du programme.
La correction de cet exercice est disponible en téléchargement depuis l’onglet Compléments. Étant donné une liste de nombres entiers, répartissez ces éléments dans une liste IMPAIR contenant les nombres impairs et une liste PAIR contenant les nombres pairs. Affichez le résultat. Pour tester si un nombre k est pair, l’expression k%2 vous sera utile.
La correction de cet exercice est disponible en téléchargement depuis l’onglet Compléments. Pour une liste de nombres entiers compris entre 0 et 9, comptez le nombre d’occurrences de chaque nombre dans la liste. Ainsi, pour la liste L = [2, 5, 2, 5, 5, 8], nous avons deux occurrences du chiffre 2, trois occurrences du chiffre 5 et une occurrence du chiffre 8. Nous vous proposons deux approches, vous pouvez programmer l’une ou l’autre :
-
Effectuez d’abord une boucle principale allant de 0 à 9. Ensuite, faites une boucle imbriquée qui compte le nombre d’occurrences de ce nombre dans la liste ou utilisez la fonction count(). Si le chiffre apparaît au moins une fois, affichez son nombre d’occurrences.
-
Initialisez une liste avec dix valeurs 0, avec la syntaxe L=[0]*10 par exemple. Chaque case de cette liste sert à compter le nombre d’occurrences pour chaque chiffre de 0 à 9. Ensuite, faites une boucle pour parcourir la liste. Chaque fois que vous lisez une valeur v, augmentez le compteur correspondant. Une fois la liste parcourue, parcourez la liste des comptages et affichez les chiffres qui sont apparus au moins une fois.
La correction de cet exercice est disponible en téléchargement depuis l’onglet Compléments. Nous vous proposons de faire apparaître la texture suivante composée de caractères X et d’espaces :
X X X X X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X X X X X
X X X X X X X X X X X X X X X X X X X X
Voici quelques conseils :
-
Utilisez une boucle principale pour créer 10 lignes.
-
Utilisez une boucle secondaire pour afficher 20 fois la chaîne "X ".
-
Pour éviter un retour à la ligne, utilisez le paramètre end de la fonction print().
Projets
La correction de ce projet est disponible en téléchargement depuis l’onglet Compléments. Un cryptarithme est une suite d’opérations arithmétiques entre des mots dans lesquels chaque lettre correspond à un chiffre. Le codage doit respecter les règles suivantes : une même lettre représente toujours le même chiffre, deux lettres différentes représentent deux chiffres différents. Le but du jeu est de trouver une correspondance entre lettres et chiffres donnant un résultat exact. Voici un exemple :
|
N |
E |
U |
F |
|
N -} 1 E -} 5 |
|
1 |
5 |
7 |
2 |
+ |
D |
E |
U |
X |
|
F -} 2 D -} 6 |
|
6 |
5 |
7 |
3 |
|
-- |
-- |
-- |
- |
|
X -} 3 U -} 7 |
|
-- |
-- |
-- |
-- |
|
O |
N |
Z |
E |
|
Z -} 4 O -} 8 |
|
8 |
1 |
4 |
5 |
Nous vous proposons d’écrire un programme pour résoudre le cryptarithme : UN + UN + NEUF = ONZE.
Conseils :
-
Créez des variables pour chaque lettre portant par exemple le même nom.
-
Vous avez en tout six lettres : U, N, E, F, O, Z et vous devez tester pour chacune d’elles les dix chiffres possibles. Vous pouvez un peu optimiser pour la lettre U et la lettre O, car elles ne peuvent être égales à zéro parce qu’elles sont en début de nombre. Ainsi, vous devez générer une boucle for pour chaque lettre et ainsi imbriquer six boucles for.
-
Pensez à vérifier que les lettres correspondent toutes à des chiffres différents. Pour cela, il n’y a pas de méthode élégante à ce stade, il faudra effectuer les quinze tests nécessaires.
-
Une fois la valeur de chaque variable positionnée, vous pouvez facilement calculer la valeur de ONZE en faisant O * 1000 + N * 100 + Z * 10 + E.
Vous trouverez de nombreux autres cryptarithmes sur la page web de Nicolas Graner ou sur Wikipédia. Maintenant, vous allez pouvoir tous les résoudre !
Correction :
for U in range(1,10) :
for N in range(10) :
for E in range(10) :
for F in range(10) :
for O in range(1,10) :
for Z in range(10) :
dif1 = U != N and U != E and U != F and U != O and U!=Z
dif2 = N != E and N != F and N != O and N != Z
dif3 = E != F and E != O and E != Z
dif4 = F != O and F != Z and O != Z
if dif1 and dif2 and dif3 and dif4 :
ONZE = O * 1000 + N * 100 + Z * 10 + E
UN = U * 10 + N
NEUF = N * 1000 + E * 100 + U * 10 + F
if UN + UN + NEUF == ONZE :
print(UN,"+",UN,"+",NEUF,"=",ONZE )
>>> 81 + 81 + 1987 = 2149