Apprendre à programmer avec Python/Approfondir les structures de données
Un livre de Wikibooks.
Jusqu'à présent, nous nous sommes contentés d'opérations assez simples. Nous allons maintenant passer à la vitesse supérieure. Les structures de données que vous utilisez déjà présentent quelques caractéristiques que vous ne connaissez pas encore, et il est également temps de vous faire découvrir d'autres structures plus complexes.
[modifier] Le point sur les chaînes de caractères
Nous avons déjà rencontré les chaînes de caractères au chapitre
. A la différence des données numériques, qui sont des entités singulières, les chaînes de caractères (ou string) constituent un type de donnée composite. Nous entendons par là une entité bien définie qui est faite elle-même d'un ensemble d'entités plus petites, en l'occurrence : les caractères. Suivant les circonstances, nous serons amenés à traiter une telle donnée composite, tantôt comme un seul objet, tantôt comme une suite ordonnée d'éléments. Dans ce dernier cas, nous souhaiterons probablement pouvoir accéder à chacun de ces éléments à titre individuel.
En fait, les chaînes de caractères font partie d'une catégorie d'objets Python que l'on appelle des séquences, et dont font partie aussi les listes et les tuples. On peut effectuer sur les séquences tout un ensemble d'opérations similaires. Vous en connaissez déjà quelques unes, et nous allons en décrire quelques autres dans les paragraphes suivants.
[modifier] Concaténation, Répétition
Les chaînes peuvent être concaténées avec l'opérateur + et répétées avec l'opérateur * :
>>> n = 'abc' + 'def' # concaténation >>> m = 'zut ! ' * 4 # répétition >>> print n, m abcdef zut ! zut ! zut ! zut !
Remarquez au passage que les opérateurs + et * peuvent aussi être utilisés pour l'addition et la multiplication lorsqu'ils s'appliquent à des arguments numériques. Le fait que les mêmes opérateurs puissent fonctionner différemment en fonction du contexte dans lequel on les utilise est un mécanisme fort intéressant que l'on appelle surcharge des opérateurs. Dans d'autres langages, la surcharge des opérateurs n'est pas toujours possible : on doit alors utiliser des symboles différents pour l'addition et la concaténation, par exemple.
[modifier] Indiçage, extraction, longueur
Les chaînes sont des séquences de caractères. Chacun de ceux-ci occupe une place précise dans la séquence. Sous Python, les éléments d'une séquence sont toujours indicés (ou numérotés) de la même manière, c'est-à-dire à partir de zéro. Pour extraire un caractère d'une chaîne, il suffit d'indiquer son indice entre crochets :
>>> nom = 'Cédric' >>> print nom[1], nom[3], nom[5] é r c
Il arrive aussi très fréquemment, lorsque l'on travaille avec des chaînes, que l'on souhaite extraire une petite chaîne hors d'une chaîne plus longue. Python propose pour cela une technique simple que l'on appelle slicing (« découpage en tranches »). Elle consiste à indiquer entre crochets les indices correspondant au début et à la fin de la « tranche » que l'on souhaite extraire :
>>> ch = "Juliette" >>> print ch[0:3] Jul
Dans la tranche [n,m], le nième caractère est inclus, mais pas le mième. Si vous voulez mémoriser aisément ce mécanisme, il faut vous représenter que les indices pointent des emplacements situés entre les caractères, comme dans le schéma ci-dessous :
Au vu de ce schéma, il n'est pas difficile de comprendre que ch[3:7] extraira iett.
Les indices de découpage ont des valeurs par défaut : un premier indice non défini est considéré comme zéro, tandis que le second indice omis prend par défaut la taille de la chaîne complète :
>>> print ch[:3] # les 3 premiers caractères Jul >>> print ch[3:] # tout sauf les 3 premiers caractères iette
Exercices
- Déterminez vous-même ce qui se passe lorsque l'un ou l'autre des indices de découpage est erroné, et décrivez cela le mieux possible. (Si le second indice est plus petit que le premier, par exemple, ou bien si le second indice est plus grand que la taille de la chaîne).
- Découpez une grande chaîne en fragments de 5 caractères chacun. Rassemblez ces morceaux dans l'ordre inverse.
- Tâchez d'écrire une petite fonction trouve() qui fera exactement le contraire de ce que fait l'opérateur d'indexage (c'est-à-dire les crochets []). Au lieu de partir d'un index donné pour retrouver le caractère correspondant, cette fonction devra retrouver l'index correspondant à un caractère donné.
En d'autres termes, il s'agit d'écrire une fonction qui attend deux arguments : le nom de la chaîne à traiter et le caractère à trouver. La fonction doit fournir en retour l'index du premier caractère de ce type dans la chaîne. Ainsi par exemple, l'instruction :
print trouve("Juliette & Roméo", "&")devra afficher : 9
Attention : Il faut penser à tous les cas possibles. Il faut notamment veiller à ce que la fonction renvoie une valeur particulière (par exemple la valeur -1) si le caractère recherché n'existe pas dans la chaîne traitée. - Améliorez la fonction de l'exercice précédent en lui ajoutant un troisième paramètre : l'index à partir duquel la recherche doit s'effectuer dans la chaîne. Ainsi par exemple, l'instruction :
print trouve ("César & Cléopâtre", "r", 5)devra afficher : 15 (et non 4 !) - Écrivez une fonction comptecar() qui compte le nombre d'occurrences d'un caractère donné dans une chaîne. Ainsi l'instruction :
print comptecar("ananas au jus","a")devra afficher : 4
Solution
- Réfléchissez !
-
#(découpage d'une chaîne en fragments) : def decoupe(ch, n): "découpage de la chaîne ch en une liste de fragments de n caractères" d, f = 0, n # indices de début et de fin de fragment tt = [] # liste à construire while d < len(ch): if f > len(ch): # on ne peut pas découper au-delà de la fin f = len(ch) fr = ch[d:f] # découpage d'un fragment tt.append(fr) # ajout du fragment à la liste d, f = f, f +n # indices suivants return tt def inverse(tt): "rassemble les éléments de la liste tt dans l'ordre inverse" ch = "" # chaîne à construire i = len(tt) # on commence par la fin de la liste while i > 0 : i = i - 1 # le dernier élément possède l'indice n -1 ch = ch + tt[i] return ch # Test : ch ="abcdefghijklmnopqrstuvwxyz123456789" liste = decoupe(ch, 5) print liste print inverse(liste) -
# Rechercher l'indice d'un caractère dans une chaîne def trouve(ch, car, deb=0): "trouve l'indice du caractère car dans la chaîne ch" i = deb while i < len(ch): if ch[i] == car: return i # le caractère est trouvé -> on termine i = i + 1 return -1 # toute la chaîne a été scannée sans succès # Tests : print trouve("Coucou c'est moi", "z") print trouve("Juliette & Roméo", "&") print trouve("César & Cléopâtre", "r", 5) - Réfléchissez !
- Réfléchissez !
[modifier] Parcours d'une séquence. L'instruction for ... in ...
Il arrive très souvent que l'on doive traiter l'intégralité d'une chaîne caractère par caractère, du premier jusqu'au dernier, pour effectuer à partir de chacun d'eux une opération quelconque. Nous appellerons cette opération un parcours. En nous limitant aux outils Python que nous connaissons déjà, nous pouvons envisager d'encoder un tel parcours sur la base de l'instruction while :
nom = 'Jacqueline'
index = 0
while index < len(nom):
print nom[index] + ' *',
index = index +1
Cette boucle « parcourt » donc la chaîne nom pour en extraire un à un tous les caractères, lesquels sont ensuite imprimés avec interposition d'astérisques. Notez bien que la condition utilisée avec l'instruction while est index < len(nom), ce qui signifie que le bouclage doit s'effectuer jusqu'à ce que l'on soit arrivé à l'indice numéro 9 (la chaîne compte en effet 10 caractères). Nous aurons effectivement traité tous les caractères de la chaîne, puisque ceux-ci sont indicés de zéro à 9.
Le parcours d'une séquence est une opération très fréquente en programmation. Pour en faciliter l'écriture, Python vous propose une structure de boucle plus appropriée, basée sur le couple d'instructions for ... in ... :
Avec ces instructions, le programme ci-dessus devient :
nom = 'Jacqueline'
for caract in nom:
print caract + ' *',
Comme vous pouvez le constater, cette structure de boucle est plus compacte. Elle vous évite d'avoir à définir et à incrémenter une variable spécifique (un « compteur ») pour gérer l'indice du caractère que vous voulez traiter à chaque itération. La variable caract contiendra successivement tous les caractères de la chaîne, du premier jusqu'au dernier.
L'instruction for permet donc d'écrire des boucles, dans lesquelles l'itération traite successivement tous les éléments d'une séquence donnée. Dans l'exemple ci-dessus, la séquence était une chaîne de caractères. L'exemple ci-après démontre que l'on peut appliquer le même traitement aux listes (et il en sera de même pour les tuples étudiés plus loin) :
liste = ['chien','chat','crocodile']
for animal in liste:
print 'longueur de la chaîne', animal, '=', len(animal)
L'exécution de ce script donne :
longueur de la chaîne chien = 5 longueur de la chaîne chat = 4 longueur de la chaîne crocodile = 9
L'instruction for est un nouvel exemple d'instruction composée. N'oubliez donc pas le double point obligatoire à la fin de la ligne, et l'indentation du bloc d'instructions qui suit.
Le nom qui suit le mot réservé in est celui de la séquence qu'il faut traiter. Le nom qui suit le mot réservé for est celui que vous choisissez pour la variable destinée à contenir successivement tous les éléments de la séquence. Cette variable est définie automatiquement (c'est-à-dire qu'il est inutile de la définir au préalable), et son type est automatiquement adapté à celui de l'élément de la séquence qui est en cours de traitement (rappel : dans le cas d'une liste, tous les éléments ne sont pas nécessairement du même type).
Exemple :
divers = ['cheval', 3, 17.25, [5, 'Jean']]
for e in divers:
print e
L'exécution de ce script donne :
cheval 3 17.25 [5, 'Jean']
Bien que les éléments de la liste divers soient tous de types différents (une chaîne de caractères, un entier, un réel, une liste), on peut affecter successivement leurs contenus à la variable e, sans qu'il s'ensuive des erreurs (ceci est rendu possible grâce au typage dynamique des variables Python).
Exercices
- Dans un conte américain, huit petits canetons s'appellent respectivement : Jack, Kack, Lack, Mack, Nack, Oack, Pack et Qack. Écrivez un script qui génère tous ces noms à partir des deux chaînes suivantes :
prefixes = 'JKLMNOP'etsuffixe = 'ack'Si vous utilisez une instructionfor ... in ..., votre script ne devrait comporter que deux lignes. - Rechercher le nombre de mots contenus dans une phrase donnée.
Solution
-
prefixes, suffixe = "JKLMNOP", "ack" for p in prefixes: print p + suffixe -
def compteMots(ch): "comptage du nombre de mots dans la chaîne ch" if len(ch) ==0: return 0 nm = 1 # la chaîne comporte au moins un mot for c in ch: if c == " ": # il suffit de compter les espaces nm = nm + 1 return nm # Test : print compteMots("Les petits ruisseaux font les grandes rivières")
[modifier] Appartenance d'un élément à une séquence. L'instruction in utilisée seule
L'instruction in peut être utilisée indépendamment de for, pour vérifier si un élément donné fait partie ou non d'une séquence. Vous pouvez par exemple vous servir de in pour vérifier si tel caractère alphabétique fait partie d'un groupe bien déterminé :
car = "e"
voyelles = "aeiouyAEIOUY"
if car in voyelles:
print car, "est une voyelle"
D'une manière similaire, vous pouvez vérifier l'appartenance d'un élément à une liste :
n = 5
premiers = [1, 2, 3, 5, 7, 11, 13, 17]
if n in premiers:
print n, "fait partie de notre liste de nombres premiers"
Cette instruction très puissante effectue donc à elle seule un véritable parcours de la séquence. À titre d'exercice, écrivez les instructions qui effectueraient le même travail à l'aide d'une boucle classique utilisant l'instruction while.
Exercices
Note : dans les exercices ci-après, omettez délibérément les caractères accentués et spéciaux.
- Écrivez une fonction majuscule() qui renvoie « vrai » si l'argument transmis est une majuscule.
- Écrivez une fonction qui renvoie « vrai » si l'argument transmis est un chiffre.
- Écrivez une fonction qui convertit une phrase en une liste de mots.
- Utilisez les fonctions définies dans les exercices précédents pour écrire un script qui puisse extraire d'un texte tous les mots qui commencent par une majuscule.
Solution
-
def majuscule(car): "renvoie <vrai> si car est une majuscule" if car in "ABCDEFGHIJKLMNOPQRSTUVWXYZ": return 1 else: return 0Réfléchissez ! -
def chaineListe(ch): "convertit la chaîne ch en une liste de mots" liste, ct = [], "" # ct est une chaîne temporaire for c in ch: if c == " ": liste.append(ct) # ajouter la ch. temporaire à la liste ct = "" # ré-initialiser la ch. temporaire else: ct = ct + c if ct != "": liste.append(ct) # ne pas oublier le dernier mot return liste # Test : print chaineListe("Une hirondelle ne fait pas le printemps") print chaineListe("") -
# (utilise les deux fonctions définies dans les exercices précédents) : txt = "Le nom de ce Monsieur est Alphonse" lst = chaineListe(txt) # convertir la phrase en une liste de mots for mot in lst: # analyser chacun des mots de la liste if majuscule(mot[0]): # tester le premier caractère du mot print mot
[modifier] Les chaînes sont des séquences non modifiables
Vous ne pouvez pas modifier le contenu d'une chaîne existante. En d'autres termes, vous ne pouvez pas utiliser l'opérateur [] dans la partie gauche d'une instruction d'affectation. Essayez par exemple d'exécuter le petit script suivant (qui cherche à remplacer une lettre dans une chaîne) :
salut = 'bonjour à tous' salut[0] = 'B' print salut
Au lieu d'afficher « Bonjour à tous », ce script « lève » une erreur du genre : TypeError: object doesn't support item assignment. Cette erreur est provoquée à la deuxième ligne du script. On y essaie de remplacer une lettre par une autre dans la chaîne, mais cela n'est pas permis.
Par contre, le script ci-dessous fonctionne :
salut = 'bonjour à tous' salut = 'B' + salut[1:] print salut
Dans cet autre exemple, en effet, nous ne modifions pas la chaîne salut. Nous en re-créons une nouvelle avec le même nom à la deuxième ligne du script (à partir d'un morceau de la précédente, soit, mais qu'importe : il s'agit bien d'une nouvelle chaîne).
[modifier] Les chaînes sont comparables
Tous les opérateurs de comparaison dont nous avons parlé à propos des instructions de contrôle de flux (c'est-à-dire les instructions if ... elif ... else) fonctionnent aussi avec les chaînes de caractères. Cela vous sera très utile pour trier des mots par ordre alphabétique :
mot = raw_input("Entrez un mot quelconque : ")
if mot < "limonade":
place = "précède"
elif mot > "limonade":
place = "suit"
else:
place = "se confond avec"
print "Le mot", mot, place, "le mot 'limonade' dans l'ordre alphabétique"
Ces comparaisons sont possibles, parce que les caractères alphabétiques qui constituent une chaîne de caractères sont mémorisés dans la mémoire de l'ordinateur sous forme de nombres binaires dont la valeur est liée à la place qu'occupe le caractère dans l'alphabet. Dans le système de codage ASCII, par exemple, A=65, B=66, C=67, etc.[1]
[modifier] Classement des caractères
Il est souvent utile de pouvoir déterminer si tel caractère extrait d'une chaîne est une lettre majuscule ou minuscule, ou plus généralement encore, de déterminer s'il s'agit bien d'une lettre, d'un chiffre, ou encore d'un autre caractère typographique. Nous pouvons bien entendu écrire différentes fonctions pour assurer ces tâches. Une première possibilité consiste à utiliser l'instruction in comme nous l'avons vu dans un précédent paragraphe. Mais puisque nous savons désormais que les caractères forment une suite bien ordonnée dans le code ASCII, nous pouvons exploiter d'autres méthodes. Par exemple, la fonction ci-dessous renvoie « vrai » si l'argument qu'on lui passe est une minuscule :
def minuscule(ch):
if 'a' <= ch <= 'z' :
return 1
else:
return 0
Exercices
Note : dans les exercices ci-après, omettez délibérément les caractères accentués et spéciaux.
- Écrivez une fonction
majuscule()qui renvoie « vrai » si l'argument transmis est une majuscule (utilisez une autre méthode que celle exploitée précédemment) - Écrivez une fonction qui renvoie « vrai » si l'argument transmis est un caractère alphabétique quelconque (majuscule ou minuscule). Dans cette nouvelle fonction, utilisez les fonctions minuscule() et majuscule() définies auparavant.
- Écrivez une fonction qui renvoie « vrai » si l'argument transmis est un chiffre.
- Écrivez une fonction qui renvoie le nombre de caractères majuscules contenus dans une phrase donnée en argument.
Solution
-
def majuscule(car): "renvoie <vrai> si car est une majuscule" if car >= "A" and car <= "Z": return 1 else: return 0 def minuscule(car): "renvoie <vrai> si car est une minuscule" if car >= "a" and car <= "z": return 1 else: return 0 def alphab(car): "renvoie <vrai> si car est un caractère alphabétique" if majuscule(car) or minuscule(car): return 1 else: return 0 - Réfléchissez !
- Réfléchissez !
-
(utilise deux fonctions définies dans les exercices précédents) : def compteMaj(ch): "comptage des mots débutant par une majuscule dans la chaîne ch" c = 0 lst = chaineListe(ch) # convertir la phrase en une liste de mots for mot in lst: # analyser chacun des mots de la liste if majuscule(mot[0]): # tester le premier caractère du mot c = c +1 return c # Test : print compteMaj("Les filles Tidgout se nomment Justine et Corinne")
Afin que vous puissiez effectuer plus aisément toutes sortes de traitements sur les caractères, Python met à votre disposition un certain nombre de fonctions prédéfinies :
- La fonction
ord(ch)accepte n'importe quel caractère comme argument. En retour, elle fournit le code ASCII correspondant à ce caractère. Ainsiord('A')renvoie la valeur65.
- La fonction
chr(num)fait exactement le contraire. L'argument qu'on lui transmet doit être un entier compris entre 0 et 255. En retour, on obtient le caractère ASCII correspondant. Ainsichr(65)renvoie le caractèreA.
Exercices
Note : dans les exercices ci-après, omettez délibérément les caractères accentués et spéciaux.
- Écrivez un petit script qui affiche une table des codes ASCII. Le programme doit afficher tous les caractères en regard des codes correspondants. A partir de cette table, établissez les relations numériques reliant chaque caractère majuscule à chaque caractère minuscule.
- À partir des relations trouvées dans l'exercice précédent, écrivez une fonction qui convertit tous les caractères d'une phrase donnée en minuscules.
- À partir des mêmes relations, écrivez une fonction qui convertit tous les caractères minuscules en majuscules, et vice-versa (dans une phrase fournie en argument).
- Écrivez une fonction qui compte le nombre de fois qu'apparaît tel caractère (fourni en argument) dans une phrase donnée.
- Écrivez une fonction qui renvoie le nombre de voyelles contenues dans une phrase donnée.
Solution
-
#(table des codes ASCII) : c = 32 # Premier code ASCII <imprimable> while c < 128 : # caractères non accentués seulement print "Code", c, ":", chr(c), " ", c = c + 1 - Réfléchissez !
-
#(Convertir majuscules -> minuscules et inversément) : def convMajMin(ch): "échange les majuscules et les minuscules dans la chaîne ch" nouvC = "" # chaîne à construire for car in ch: code = ord(car) if car >= "A" and car <= "Z": code = code + 32 elif car >= "a" and car <= "z": code = code - 32 nouvC = nouvC + chr(code) return nouvC # Test : print convMajMin("Ferdinand-Charles de CAMARET") - Réfléchissez !
-
#(Comptage de voyelles) : def voyelle(car): "teste si car est une voyelle" if car in "AEIOUYaeiouy": return 1 else: return 0 def compteVoyelles(ch): "compte les voyelles présentes dans la chaîne ch" n = 0 for c in ch: if voyelle(c): n = n + 1 return n # Test : print compteVoyelles("Monty Python Flying Circus")
[modifier] Les chaînes sont des objets
Dans les chapitres précédents, vous avez déjà rencontré de nombreux objets. Vous savez donc que l'on peut agir sur un objet à l'aide de méthodes (c'est-à-dire des fonctions associées à cet objet).
Sous Python, les chaînes de caractères sont des objets. On peut donc effectuer de nombreux traitements sur les chaînes de caractères en utilisant des méthodes appropriées. En voici quelques-unes, choisies parmi les plus utiles[2] :
split(): convertit une chaîne en une liste de sous-chaînes. On peut choisir le caractère séparateur en le fournissant comme argument, sinon c'est un espace, par défaut :
>>> c2 ="Votez pour moi"
>>> a = c2.split()
>>> print a
['Votez', 'pour', 'moi']
>>> c4 ="Cet exemple, parmi d'autres, peut encore servir"
>>> c4.split(",")
['Cet exemple', " parmi d'autres", ' peut encore servir']
join(liste): rassemble une liste de chaînes en une seule (Cette méthode fait donc l'inverse de la précédente). Attention : la chaîne à laquelle on applique cette méthode est celle qui servira de séparateur (un ou plusieurs caractères); l'argument transmis est la liste des chaînes à rassembler :
>>> b2 = ["Salut","les","copains"] >>> print " ".join(b2) Salut les copains >>> print "---".join(b2) Salut---les---copains
find(sch): cherche la position d'une sous-chaîneschdans la chaîne :
>>> ch1 = "Cette leçon vaut bien un fromage, sans doute ?" >>> ch2 = "fromage" >>> print ch1.find(ch2) 25
count(sch): compte le nombre de sous-chaînesschdans la chaîne :
>>> ch1 = "Le héron au long bec emmanché d'un long cou" >>> ch2 = 'long' >>> print ch1.count(ch2) 2
lower(): convertit une chaîne en minuscules :
>>> ch ="ATTENTION : Danger !" >>> print ch.lower() attention : danger !
upper(): convertit une chaîne en majuscules :
>>> ch = "Merci beaucoup" >>> print ch.upper() MERCI BEAUCOUP
capitalize(): convertit en majuscule la première lettre d'une chaîne :
>>> b3 = "quel beau temps, aujourd'hui !" >>> print b3.capitalize() "Quel beau temps, aujourd'hui !"
swapcase(): convertit toutes les majuscules en minuscules et vice-versa :
>>> ch5 = "La CIGALE et la FOURMI" >>> print ch5.swapcase() lA cigale ET LA fourmi
strip(): enlève les espaces éventuels au début et à la fin de la chaîne :
>>> ch = " Monty Python " >>> ch.strip() 'Monty Python'
replace(c1, c2): remplace tous les caractèresc1par des caractèresc2dans la chaîne :
>>> ch8 = "Si ce n'est toi c'est donc ton frère"
>>> print ch8.replace(" ","*")
Si*ce*n'est*toi*c'est*donc*ton*frère
index(c): retrouve l'index de la première occurrence du caractère c dans la chaîne :
>>> ch9 ="Portez ce vieux whisky au juge blond qui fume"
>>> print ch9.index("w")
16
Dans la plupart de ces méthodes, il est possible de préciser quelle portion de la chaîne doit être traitée, en ajoutant des arguments supplémentaires. Exemple :
>>> print ch9.index("e") # cherche à partir du début de la chaîne
4 # et trouve le premier 'e'
>>> print ch9.index("e",5) # cherche seulement à partir de l'indice 5
8 # et trouve le second 'e'
>>> print ch9.index("e",15) # cherche à partir du caractère n° 15
29 # et trouve le quatrième 'e'
Etc., etc.
Comprenez bien qu'il n'est pas possible de décrire toutes les méthodes disponibles ainsi que leur paramétrage dans le cadre de ce cours. Si vous souhaitez en savoir davantage, il vous faut consulter la documentation en ligne de Python (Library reference), ou un bon ouvrage de référence (comme par exemple Python en concentré d'Alex Martelli – Éditions O'Reilly).
- Fonctions intégrées
À toutes fins utiles, rappelons également ici que l'on peut aussi appliquer aux chaînes un certain nombre de fonctions intégrées dans le langage lui-même :
len(ch)renvoie la longueur de la chaîne ch (c'est-à-dire son nombre de caractères) ;
float(ch)convertit la chaîne ch en un nombre réel (float) (bien entendu, cela ne pourra fonctionner que si la chaîne représente bien un tel nombre) :
>>> a = float("12.36")
>>> print a + 5
17.36
int(ch)convertit la chaînechen un nombre entier :
>>> a = int("184")
>>> print a + 20
204
[modifier] Formatage des chaînes de caractères
Pour terminer ce tour d'horizon des fonctionnalités associées aux chaînes de caractères, il nous semble utile de vous présenter encore une technique que l'on appelle formatage. Cette technique se révèle particulièrement utile dans tous les cas où vous devez construire une chaîne de caractères complexe à partir d'un certain nombre de morceaux, tels que les valeurs de variables diverses.
Considérons par exemple que vous avez écrit un programme qui traite de la couleur et de la température d'une solution aqueuse, en chimie. La couleur est mémorisée dans une chaîne de caractères nommée coul, et la température dans une variable nommée temp (variable de type float). Vous souhaitez à présent que votre programme construise une nouvelle chaîne de caractères à partir de ces données, par exemple une phrase telle que la suivante : « La solution est devenue rouge et sa température atteint 12,7 °C ».
Vous pouvez construire cette chaîne en assemblant des morceaux à l'aide de l'opérateur de concaténation (le symbole +), mais il vous faudra aussi utiliser la fonction str() pour convertir en chaîne de caractères la valeur numérique contenue dans la variable de type float (faites l'exercice).
Python vous offre une autre possibilité. Vous pouvez construire votre chaîne en assemblant deux éléments à l'aide de l'opérateur % : à gauche vous fournissez une chaîne de formatage (un patron, en quelque sorte) qui contient des marqueurs de conversion, et à droite (entre parenthèses) un ou plusieurs objets que Python devra insérer dans la chaîne, en lieu et place des marqueurs.
- Exemple
>>> coul ="verte" >>> temp = 1.347 + 15.9 >>> print "La couleur est %s et la température vaut %s °C" % (coul,temp) La couleur est verte et la température vaut 17.247 °C
Dans cet exemple, la chaîne de formatage contient deux marqueurs de conversion %s qui seront remplacés respectivement par les contenus des deux variables coul et temp.
Le marqueur %s accepte n'importe quel objet (chaîne, entier, float, ...). Vous pouvez expérimenter d'autres mises en forme en utilisant d'autres marqueurs. Essayez par exemple de remplacer le deuxième %s par %d , ou par %8.2f, ou encore par %8.2g. Le marqueur %d attend un nombre et le convertit en entier ; les marqueurs %f et %g attendent des réels et peuvent déterminer la largeur et la précision qui seront affichées.
La description complète de toutes les possibilités de formatage sort du cadre de ces notes. S'il vous faut un formatage très particulier, veuillez consulter la documentation en ligne de Python ou des manuels plus spécialisés.
Exercices
- Écrivez un script qui compte dans un fichier texte quelconque le nombre de lignes contenant des caractères numériques.
- Écrivez un script qui compte le nombre de mots contenus dans un fichier texte.
- Écrivez un script qui recopie un fichier texte en veillant à ce que chaque ligne commence par une majuscule.
- Écrivez un script qui recopie un fichier texte en fusionnant (avec la précédente) les lignes qui ne commencent pas par une majuscule.
- Vous disposez d'un fichier contenant des valeurs numériques. Considérez que ces valeurs sont les diamètres d'une série de sphères. Écrivez un script qui utilise les données de ce fichier pour en créer un autre, organisé en lignes de texte qui exprimeront « en clair » les autres caractéristiques de ces sphères (surface de section, surface extérieure et volume), dans des phrases telles que :
Diam. 46.20 cm Section = 1676.39 cm² Surf. = 6705.54 cm². Vol. = 51632.67 cm³
Diam. 120.00 cm Section = 11309.73 cm² Surf. = 45238.93 cm². Vol. = 904778.68 cm³
Diam. 0.03 cm Section = 0.00 cm² Surf. = 0.00 cm². Vol. = 0.00 cm³
Diam. 13.90 cm Section = 151.75 cm² Surf. = 606.99 cm². Vol. = 1406.19 cm³
Diam. 88.80 cm Section = 6193.21 cm² Surf. = 24772.84 cm². Vol. = 366638.04 cm³
etc. - Vous avez à votre disposition un fichier texte dont les lignes représentent des valeurs numériques de type réel, sans exposant (et encodées sous forme de chaînes de caractères).
Écrivez un script qui recopie ces valeurs dans un autre fichier en les arrondissant de telle sorte que leur partie décimale ne comporte plus qu'un seul chiffre après la virgule, celui-ci ne pouvant être que 0 ou 5 (l'arrondi doit être correct).
Solution
- Réfléchissez !
-
# Comptage du nombre de mots dans un texte fiSource = raw_input("Nom du fichier à traiter : ") fs = open(fiSource, 'r') n = 0 # variable compteur while 1: ch = fs.readline() if ch == "": break # conversion de la chaîne lue en une liste de mots : li = ch.split() # totalisation des mots : n = n + len(li) fs.close() print "Ce fichier texte contient un total de %s mots" % (n) -
# Conversion en majuscule du premier caractère de chaque ligne fiSource = raw_input("Nom du fichier à traiter : ") fiDest = raw_input("Nom du fichier destinataire : ") fs = open(fiSource, 'r') fd = open(fiDest, 'w') while 1: ch = fs.readline() if ch == "": break if ch[0] >= "A" and ch[0] <= "Z": # le premier car. est une majuscule. On passe. pass else: # Reconstruction de la chaîne: pc = ch[0].upper() # Premier caractère converti rc = ch[1:] # toute le reste de la chaîne ch = pc + rc # fusion # variante utilisant une méthode encore plus intégrée : # ch = ch.capitalize() # Transcription : fd.write(ch) fd.close() fs.close() -
# Fusion de lignes pour former des phrases fiSource = raw_input("Nom du fichier à traiter : ") fiDest = raw_input("Nom du fichier destinataire : ") fs = open(fiSource, 'r') fd = open(fiDest, 'w') # On lit d'abord la première ligne : ch1 = fs.readline() # On lit ensuite les suivantes, en les fusionnant si nécessaire : while 1: ch2 = fs.readline() if ch2 == "": break # Si la chaîne lue commence par une majuscule, on transcrit # la précédente dans le fichier destinataire, et on la # remplace par celle que l'on vient de lire : if ch2[0] >= "A" and ch2[0] <= "Z": fd.write(ch1) ch1 = ch2 # Sinon, on la fusionne avec la précédente : else: ch1 = ch1[:-1] + " " + ch2 # (veiller à enlever de ch1 le caractère de fin de ligne) fd.write(ch1) # ne pas oublier de transcrire la dernière ! fd.close() fs.close() -
#(caractéristiques de sphères) : # Le fichier de départ est un fichier <texte> dont chaque ligne contient # un nombre réel (encodé sous la forme d'une chaîne de caractères) from math import pi def caractSphere(d): "renvoie les caractéristiques d'une sphère de diamètre d" d = float(d) # conversion de l'argument (=chaîne) en réel r = d/2 # rayon ss = pi*r**2 # surface de section se = 4*pi*r**2 # surface extérieure v = 4./3*pi*r**3 # volume (! la 1e division doit être réelle !) # Le marqueur de conversion %8.2f utilisé ci-dessous formate le nombre # affiché de manière à occuper 8 caractères au total, en arrondissant # de manière à conserver deux chiffres après la virgule : ch = "Diam. %6.2f cm Section = %8.2f cm² " % (d, ss) ch = ch +"Surf. = %8.2f cm². Vol. = %9.2f cm³" % (se, v) return ch fiSource = raw_input("Nom du fichier à traiter : ") fiDest = raw_input("Nom du fichier destinataire : ") fs = open(fiSource, 'r') fd = open(fiDest, 'w') while 1: diam = fs.readline() if diam == "" or diam == "\n": break fd.write(caractSphere(diam) + "\n") # enregistrement fd.close() fs.close() -
# Mise en forme de données numériques # Le fichier traité est un fichier <texte> dont chaque ligne contient un nombre # réel (sans exposants et encodé sous la forme d'une chaîne de caractères) def arrondir(reel): "représentation arrondie à .0 ou .5 d'un nombre réel" ent = int(reel) # partie entière du nombre fra = reel - ent # partie fractionnaire if fra < .25 : fra = 0 elif fra < .75 : fra = .5 else: fra = 1 return ent + fra fiSource = raw_input("Nom du fichier à traiter : ") fiDest = raw_input("Nom du fichier destinataire : ") fs = open(fiSource, 'r') fd = open(fiDest, 'w') while 1: ligne = fs.readline() if ligne == "" or ligne == "\n": break n = arrondir(float(ligne)) # conversion en <float>, puis arrondi fd.write(str(n) + "\n") # enregistrement fd.close() fs.close()
[modifier] Le point sur les listes
Nous avons déjà rencontré les listes à plusieurs reprises, depuis leur présentation sommaire au chapitre 5. Les listes sont des collections ordonnées d'objets. Comme les chaînes de caractères, les listes font partie d'un type général que l'on appelle séquences sous Python. Comme les caractères dans une chaîne, les objets placés dans une liste sont rendus accessibles par l'intermédiaire d'un index (un nombre qui indique l'emplacement de l'objet dans la séquence).
[modifier] Définition d'une liste – Accès à ses éléments
Vous savez déjà que l'on délimite une liste à l'aide de crochets :
>>> nombres = [5, 38, 10, 25] >>> mots = ["jambon", "fromage", "confiture", "chocolat"] >>> stuff = [5000, "Brigitte", 3.1416, ["Albert", "René", 1947]]
Dans le dernier exemple ci-dessus, nous avons rassemblé un entier, une chaîne, un réel et même une liste, pour vous rappeler que l'on peut combiner dans une liste des données de n'importe quel type, y compris des listes, des dictionnaires et des tuples (ceux-ci seront étudiés plus loin).
Pour accéder aux éléments d'une liste, on utilise les mêmes méthodes (index, découpage en tranches) que pour accéder aux caractères d'une chaîne :
>>> print nombres[2] 10 >>> print nombres[1:3] [38, 10] >>> print nombres[2:3] [10] >>> print nombres[2:] [10, 25] >>> print nombres[:2] [5, 38] >>> print nombres[-1] 25 >>> print nombres[-2] 10
Les exemples ci-dessus devraient attirer votre attention sur le fait qu'une tranche découpée dans une liste est toujours elle-même une liste (même s'il s'agit d'une tranche qui ne contient qu'un seul élément, comme dans notre troisième exemple), alors qu'un élément isolé peut contenir n'importe quel type de donnée. Nous allons approfondir cette distinction tout au long des exemples suivants.
[modifier] Les listes sont modifiables
Contrairement aux chaînes de caractères, les listes sont des séquences modifiables. Cela nous permettra de construire plus tard des listes de grande taille, morceau par morceau, d'une manière dynamique (c'est-à-dire à l'aide d'un algorithme quelconque).
- Exemples
>>> nombres[0] = 17 >>> nombres [17, 38, 10, 25]
Dans l'exemple ci-dessus, on a remplacé le premier élément de la liste nombres, en utilisant l'opérateur [ ] (opérateur d'indiçage) à la gauche du signe égale.
Si l'on souhaite accéder à un élément faisant partie d'une liste, elle-même située dans une autre liste, il suffit d'indiquer les deux index entre crochets successifs :
>>> stuff[3][1] = "Isabelle" >>> stuff [5000, 'Brigitte', 3.1415999999999999, ['Albert', 'Isabelle', 1947]]
Comme c'est le cas pour toutes les séquences, il ne faut jamais oublier que la numérotation des éléments commence à partir de zéro. Ainsi, dans l'exemple ci-dessus on remplace l'élément n° 1 d'une liste, qui est elle-même l'élément n° 3 d'une autre liste : la liste stuff.
[modifier] Les listes sont des objets
Sous Python, les listes sont des objets à part entière, et vous pouvez donc leur appliquer un certain nombre de méthodes particulièrement efficaces :
>>> nombres = [17, 38, 10, 25, 72] >>> nombres.sort() # trier la liste >>> nombres [10, 17, 25, 38, 72] >>> nombres.append(12) # ajouter un élément à la fin >>> nombres [10, 17, 25, 38, 72, 12] >>> nombres.reverse() # inverser l'ordre des éléments >>> nombres [12, 72, 38, 25, 17, 10] >>> nombres.index(17) # retrouver l'index d'un élément 4 >>> nombres.remove(38) # enlever (effacer) un élément >>> nombres [12, 72, 25, 17, 10]
En plus de ces méthodes, vous disposez encore de l'instruction intégrée del, qui vous permet d'effacer un ou plusieurs éléments à partir de leur(s) indices :
>>> del nombres[2] >>> nombres [12, 72, 17, 10] >>> del nombres[1:3] >>> nombres [12, 10]
Notez bien la différence entre la méthode remove() et l'instruction del : del travaille avec un indice ou une tranche d'indices, tandis que remove() recherche une valeur (si plusieurs éléments de la liste possèdent la même valeur, seul le premier est effacé).
Exercices
- Écrivez un script qui génère la liste des carrés et des cubes des nombres de 20 à 40.
- Écrivez un script qui crée automatiquement la liste des sinus des angles de 0° à 90° , par pas de 5°. Attention : la fonction sin() du module math considère que les angles sont fournis en radians (360° = 2 radians)
- Écrivez un script qui permette d'obtenir à l'écran les 15 premiers termes des tables de multiplication par 2, 3, 5, 7, 11, 13, 17, 19 (ces nombres seront placés au départ dans une liste) sous la forme d'une table similaire à la suivante :
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
3 6 9 12 15 18 21 24 27 30 33 36 39 42 45
5 10 15 20 25 30 35 40 45 50 55 60 65 70 75
etc. - Soit la liste suivante :
['Jean-Michel', 'Marc', 'Vanessa', 'Anne', 'Maximilien', 'Alexandre-Benoît', 'Louise']
Écrivez un script qui affiche chacun de ces noms avec le nombre de caractères correspondant. - Vous disposez d'une liste de nombres entiers quelconques, certains d'entre eux étant présents en plusieurs exemplaires. Écrivez un script qui recopie cette liste dans une autre, en omettant les doublons. La liste finale devra être triée.
- Écrivez un script qui recherche le mot le plus long dans une phrase donnée (l'utilisateur du programme doit pouvoir entrer une phrase de son choix).
- Écrivez un script capable d'afficher la liste de tous les jours d'une année imaginaire, laquelle commencerait un Jeudi. Votre script utilisera lui-même trois listes : une liste des noms de jours de la semaine, une liste des noms des mois, et une liste des nombres de jours que comportent chacun des mois (ne pas tenir compte des années bissextiles).
Exemple de sortie :
Jeudi 1 Janvier Vendredi 2 Janvier Samedi 3 Janvier Dimanche 4 Janvier
... et ainsi de suite jusqu'auJeudi 31 Décembre. - Vous avez à votre disposition un fichier texte qui contient un certain nombre de noms d'élèves. Écrivez un script qui effectue une copie triée de ce fichier.
- Écrivez une fonction permettant de trier une liste. Cette fonction ne pourra pas utiliser la méthode intégrée sort() de Python : Vous devez donc définir vous-même l'algorithme de tri.
(Note : cette question devra faire l'objet d'une discussion-synthèse en classe)
Solution
- Réfléchissez !
- Réfléchissez !
-
# Affichage de tables de multiplication nt = [2, 3, 5, 7, 9, 11, 13, 17, 19] def tableMulti(m, n): "renvoie n termes de la table de multiplication par m" ch ="" for i in range(n): v = m * (i+1) # calcul d'un des termes ch = ch + "%4d" % (v) # formatage à 4 caractères return ch for a in nt: print tableMulti(a, 15) # 15 premiers termes seulement -
#(simple parcours d'une liste) : lst = ['Jean-Michel', 'Marc', 'Vanessa', 'Anne', 'Maximilien', 'Alexandre-Benoît', 'Louise'] for e in lst: print "%s : %s caractères" % (e, len(e)) -
# Elimination de doublons lst = [9, 12, 40, 5, 12, 3, 27, 5, 9, 3, 8, 22, 40, 3, 2, 4, 6, 25] lst2 = [] for el in lst: if el not in lst2: lst2.append(el) lst2.sort() print lst2 - Réfléchissez !
-
(afficher tous les jours d'une année) : ## Cette variante utilise une liste de listes ## ## (que l'on pourrait aisément remplacer par deux listes distinctes) # La liste ci-dessous contient deux éléments qui sont eux-mêmes des listes. # l'élément 0 contient les nombres de jours de chaque mois, tandis que # l'élément 1 contient les noms des douze mois : mois = [[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre']] jour = ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'] ja, jm, js, m = 0, 0, 0, 0 while ja <365: ja, jm = ja +1, jm +1 # ja = jour dans l'année, jm = jour dans le mois js = (ja +3) % 7 # js = jour de la semaine. Le décalage ajouté # permet de choisir le jour de départ if jm > mois[0][m]: # élément m de l'élément 0 de la liste jm, m = 1, m+1 print jour[js], jm, mois[1][m] # élément m de l'élément 1 de la liste - Réfléchissez !
- Réfléchissez !
[modifier] Techniques de « slicing » avancé pour modifier une liste
Comme nous venons de le signaler, vous pouvez ajouter ou supprimer des éléments dans une liste en utilisant une instruction (del) et une méthode (append()) intégrées. Si vous avez bien assimilé le principe du « découpage en tranches » (slicing), vous pouvez cependant obtenir les mêmes résultats à l'aide du seul opérateur [ ]. L'utilisation de cet opérateur est un peu plus délicate que celle d'instructions ou de méthodes dédiées, mais elle permet davantage de souplesse :
[modifier] Insertion d'un ou plusieurs éléments n'importe où dans une liste
>>> mots = ['jambon', 'fromage', 'confiture', 'chocolat'] >>> mots[2:2] =["miel"] >>> mots ['jambon', 'fromage', 'miel', 'confiture', 'chocolat'] >>> mots[5:5] =['saucisson', 'ketchup'] >>> mots ['jambon', 'fromage', 'miel', 'confiture', 'chocolat', 'saucisson', 'ketchup']
Pour utiliser cette technique, vous devez prendre en compte les particularités suivantes :
- Si vous utilisez l'opérateur
[ ]à la gauche du signe égale pour effectuer une insertion ou une suppression d'élément(s) dans une liste, vous devez obligatoirement y indiquer une « tranche » dans la liste cible (c'est-à-dire deux index réunis par le symbole :), et non un élément isolé dans cette liste.
- L'élément que vous fournissez à la droite du signe égale doit lui-même être une liste. Si vous n'insérez qu'un seul élément, il vous faut donc le présenter entre crochets pour le transformer d'abord en une liste d'un seul élément. Notez bien que l'élément
mots[1]n'est pas une liste (c'est la chaînefromage), alors que l'élémentmots[1:3]en est une.
Vous comprendrez mieux ces contraintes en analysant ce qui suit :
- Suppression / remplacement d'éléments
>>> mots[2:5] = [] # [] désigne une liste vide >>> mots ['jambon','fromage','saucisson', 'ketchup'] >>> mots[1:3] = ['salade'] >>> mots ['jambon', 'salade', 'ketchup'] >>> mots[1:] = ['mayonnaise', 'poulet', 'tomate'] >>> mots ['jambon', 'mayonnaise', 'poulet', 'tomate']
- À la première ligne de cet exemple, nous remplaçons la tranche [2:5] par une liste vide, ce qui correspond à un effacement.
- À la quatrième ligne, nous remplaçons une tranche par un seul élément. (Notez encore une fois que cet élément doit lui-même être « présenté » comme une liste).
- À la 7e ligne, nous remplaçons une tranche de deux éléments par une autre qui en comporte 3.
[modifier] Création d'une liste de nombres à l'aide de la fonction range()
Si vous devez manipuler des séquences de nombres, vous pouvez les créer très aisément à l'aide de cette fonction :
>>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
La fonction range() génère une liste de nombres entiers de valeurs croissantes. Si vous appelez range() avec un seul argument, la liste contiendra un nombre de valeurs égal à l'argument fourni, mais en commençant à partir de zéro (c'est-à-dire que range(n) génère les nombres de 0 à n-1).
Notez bien que l'argument fourni n'est jamais dans la liste générée.
On peut aussi utiliser range() avec deux, ou même trois arguments séparés par des virgules, afin de générer des séquences de nombres plus spécifiques :
>>> range(5,13) [5, 6, 7, 8, 9, 10, 11, 12] >>> range(3,16,3) [3, 6, 9, 12, 15]
Si vous avez du mal à assimiler l'exemple ci-dessus, considérez que range() attend toujours trois arguments, que l'on pourrait intituler FROM, TO et STEP. FROM est la première valeur à générer, TO est la dernière (ou plutôt la dernière + un), et STEP le « pas » à sauter pour passer d'une valeur à la suivante. S'ils ne sont pas fournis, les paramètres FROM et STEP prennent leurs valeurs par défaut, qui sont respectivement 0 et 1.
[modifier] Parcours d'une liste à l'aide de for, range() et len()
L'instruction for est l'instruction idéale pour parcourir une liste :
>>> prov = ['La','raison','du','plus','fort','est','toujours','la','meilleure']
>>> for mot in prov:
print mot,
La raison du plus fort est toujours la meilleure
Il est très pratique de combiner les fonctions range() et len() pour obtenir automatiquement tous les indices d'une séquence (liste ou chaîne). Exemple :
fable = ['Maître','Corbeau','sur','un','arbre','perché']
for index in range(len(fable)):
print index, fable[index]
L'exécution de ce script donne le résultat :
0 Maître 1 Corbeau 2 sur 3 un 4 arbre 5 perché
[modifier] Une conséquence du typage dynamique
Comme nous l'avons déjà signalé plus haut (page 125), le type de la variable utilisée avec l'instruction for est redéfini continuellement au fur et à mesure du parcours : même si les éléments d'une liste sont de types différents, on peut parcourir cette liste à l'aide de for sans qu'il ne s'ensuive une erreur, car le type de la variable de parcours s'adapte automatiquement à celui de l'élément en cours de lecture. Exemple :
>>> divers = [3, 17.25, [5, 'Jean'], 'Linux is not Windoze']
>>> for item in divers:
print item, type(item)
3 <type 'int'>
17.25 <type 'float'>
[5, 'Jean'] <type 'list'>
Linux is not Windoze <type 'str'>
Dans l'exemple ci-dessus, on utilise la fonction intégrée type() pour montrer que la variable item change effectivement de type à chaque itération (ceci est rendu possible grâce au typage dynamique des variables Python).
[modifier] Opérations sur les listes
On peut appliquer aux listes les opérateurs + (concaténation) et * (multiplication) :
>>> fruits = ['orange','citron'] >>> legumes = ['poireau','oignon','tomate'] >>> fruits + legumes ['orange', 'citron', 'poireau', 'oignon', 'tomate'] >>> fruits * 3 ['orange', 'citron', 'orange', 'citron', 'orange', 'citron']
L'opérateur * est particulièrement utile pour créer une liste de n éléments identiques :
>>> sept_zeros = [0]*7 >>> sept_zeros [0, 0, 0, 0, 0, 0, 0]
Supposons par exemple que vous voulez créer une liste B qui contienne le même nombre d'éléments qu'une autre liste A. Vous pouvez obtenir ce résultat de différentes manières, mais l'une des plus simples consistera à effectuer : B = [0]*len(A).
[modifier] Test d'appartenance
Vous pouvez aisément déterminer si un élément fait partie d'une liste à l'aide de l'instruction in :
>>> v = 'tomate'
>>> if v in legumes:
print 'OK'
OK
[modifier] Copie d'une liste
Considérons que vous disposez d'une liste fable que vous souhaitez recopier dans une nouvelle variable que vous appellerez phrase. La première idée qui vous viendra à l'esprit sera certainement d'écrire une simple affectation telle que :
>>> phrase = fable
En procédant ainsi, sachez que vous ne créez pas une véritable copie. À la suite de cette instruction, il n'existe toujours qu'une seule liste dans la mémoire de l'ordinateur. Ce que vous avez créé est seulement une nouvelle référence vers cette liste. Essayez par exemple :
>>> fable = ['Je','plie','mais','ne','romps','point'] >>> phrase = fable >>> fable[4] ='casse' >>> phrase ['Je', 'plie', 'mais', 'ne', 'casse', 'point']
Si la variable phrase contenait une véritable copie de la liste, cette copie serait indépendante de l'original et ne devrait donc pas pouvoir être modifiée par une instruction telle que celle de la troisième ligne, qui s'applique à la variable fable. Vous pouvez encore expérimenter d'autres modifications, soit au contenu de fable, soit au contenu de phrase. Dans tous les cas, vous constaterez que les modifications de l'une sont répercutées dans l'autre, et vice-versa.
En fait, les noms fable et phrase désignent tous deux un seul et même objet en mémoire. Pour décrire cette situation, les informaticiens diront que le nom phrase est un alias du nom fable.
Nous verrons plus tard l'utilité des alias. Pour l'instant, nous voudrions disposer d'une technique pour effectuer une véritable copie d'une liste. Avec les notions vues précédemment, vous devriez pouvoir en trouver une par vous-même.
Cette fonctionnalité vous permet d'améliorer la lisibilité de vos programmes. Exemple :
couleurs = ['noir', 'brun', 'rouge',
'orange', 'jaune', 'vert',
'bleu', 'violet', 'gris', 'blanc']
Exercices
- Soient les listes suivantes :
t1 = [31,28,31,30,31,30,31,31,30,31,30,31]
t2 = ['Janvier','Février','Mars','Avril','Mai','Juin',
'Juillet','Août','Septembre','Octobre','Novembre','Décembre']Écrivez un petit programme qui insère dans la seconde liste tous les éléments de la première, de telle sorte que chaque nom de mois soit suivi du nombre de jours correspondant :['Janvier',31,'Février',28,'Mars',31, etc...]. - Créez une liste
Acontenant quelques éléments. Effectuez une vraie copie de cette liste dans une nouvelle variableB. Suggestion : créez d'abord une listeBde même taille queAmais ne contenant que des zéros. Remplacez ensuite tous ces zéros par les éléments tirés deA. - Même question, mais autre suggestion : créez d'abord une liste
Bvide. Remplissez-la ensuite à l'aide des éléments deAajoutés l'un après l'autre. - Même question, autre suggestion encore : pour créer la liste B, découpez dans la liste A une tranche incluant tous les éléments (à l'aide de l'opérateur [:]).
- Un nombre premier est un nombre qui n'est divisible que par un et par lui-même. Écrivez un programme qui établisse la liste de tous les nombres premiers compris entre 1 et 1000, en utilisant la méthode du crible d'Eratosthène : - Créez une liste de 1000 éléments, chacun initialisé à la valeur 1.
- Parcourez cette liste à partir de l'élément d'indice 2 : si l'élément analysé possède la valeur 1, mettez à zéro tous les autres éléments de la liste, dont les indices sont des multiples entiers de l'indice auquel vous êtes arrivé. Lorsque vous aurez parcouru ainsi toute la liste, les indices des éléments qui seront restés à 1 seront les nombres premiers recherchés. En effet : A partir de l'indice 2, vous annulez tous les éléments d'indices pairs : 4, 6, 8, 10, etc. Avec l'indice 3, vous annulez les éléments d'indices 6, 9, 12, 15, etc., et ainsi de suite. Seuls resteront à 1 les éléments dont les indices sont effectivement des nombres premiers.
Solution
-
# Insertion de nouveaux éléments dans une liste existante t1 = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] t2 = ['Janvier','Février','Mars','Avril','Mai','Juin', 'Juillet','Août','Septembre','Octobre','Novembre','Décembre'] c, d = 1, 0 while d < 12 : t2[c:c] = [t1[d]] # ! l'élément inséré doit être une liste c, d = c+2, d+1 - Réfléchissez !
- Réfléchissez !
- Réfléchissez !
-
# Crible d'Eratosthène pour rechercher les nombres premiers de 1 à 999 # Créer une liste de 1000 éléments 1 (leurs indices vont de 0 à 999) : lst = [1]*1000 # Parcourir la liste à partir de l'élément d'indice 2: for i in range(2,1000): # Mettre à zéro les éléments suivants dans la liste, # dont les indices sont des multiples de i : for j in range(i*2, 1000, i): lst[j] = 0 # Afficher les indices des éléments restés à 1 (on ignore l'élément 0) : for i in range(1,1000): if lst[i]: print i,
[modifier] Nombres aléatoires - Histogrammes
La plupart des programmes d'ordinateur font exactement la même chose chaque fois qu'on les exécute. De tels programmes sont dits déterministes. Le déterminisme est certainement une bonne chose : nous voulons évidemment qu'une même série de calculs appliquée aux mêmes données initiales aboutisse toujours au même résultat. Pour certaines applications, cependant, nous pouvons souhaiter que l'ordinateur soit imprévisible. Le cas des jeux constitue un exemple évident, mais il en existe bien d'autres.
Contrairement aux apparences, il n'est pas facile du tout d'écrire un algorithme qui soit réellement non-déterministe (c'est-à-dire qui produise un résultat totalement imprévisible). Il existe cependant des techniques mathématiques permettant de simuler plus ou moins bien l'effet du hasard. Des livres entiers ont été écrits sur les moyens de produire ainsi un hasard « de bonne qualité ». Nous n'allons évidemment pas développer ici une telle question, mais rien ne vous empêche de consulter à ce sujet votre professeur de mathématiques.
Dans son module random, Python propose toute une série de fonctions permettant de générer des nombres aléatoires qui suivent différentes distributions mathématiques. Nous n'examinerons ici que quelques-unes d'entre elles. Veuillez consulter la documentation en ligne pour découvrir les autres. Vous pouvez importer toutes les fonctions du module par :
>>> from random import *
La fonction ci-dessous permet de créer une liste de nombres réels aléatoires, de valeur comprise entre zéro et un. L'argument à fournir est la taille de la liste :
>>> def list_aleat(n):
s = [0]*n
for i in range(n):
s[i] = random()
return s
Vous pouvez constater que nous avons pris le parti de construire d'abord une liste de zéros de taille n, et ensuite de remplacer les zéros par des nombres aléatoires.
Exercices
- Réécrivez la fonction
list_aleat()ci-dessus, en utilisant la méthodeappend()pour construire la liste petit à petit à partir d'une liste vide (au lieu de remplacer les zéros d'une liste préexistante comme nous l'avons fait). - Écrivez une fonction
imprime_liste()qui permette d'afficher ligne par ligne tous les éléments contenus dans une liste de taille quelconque. Le nom de la liste sera fourni en argument. Utilisez cette fonction pour imprimer la liste de nombres aléatoires générés par la fonctionlist_aleat(). Ainsi par exemple, l'instructionimprime_liste(liste_aleat(8))devra afficher une colonne de 8 nombres réels aléatoires.
Solution
- Réfléchissez !
- Réfléchissez !
Les nombres ainsi générés sont-ils vraiment aléatoires ? C'est difficile à dire. Si nous ne tirons qu'un petit nombre de valeurs, nous ne pouvons rien vérifier. Par contre, si nous utilisons un grand nombre de fois la fonction random(), nous nous attendons à ce que la moitié des valeurs produites soient plus grandes que 0,5 (et l'autre moitié plus petites).
Affinons ce raisonnement. Les valeurs tirées sont toujours dans l'intervalle 0-1. Partageons cet intervalle en 4 fractions égales : de 0 à 0,25 , de 0,25 à 0,5 , de 0,5 à 0,75 , et de 0,75 à 1. Si nous tirons un grand nombre de valeurs au hasard, nous nous attendons à ce qu'il y en ait autant qui se situent dans chacune de nos 4 fractions. Et nous pouvons généraliser ce raisonnement à un nombre quelconque de fractions, du moment qu'elles soient égales.
Exercices
- Vous allez écrire un programme destiné à vérifier le fonctionnement du générateur de nombres aléatoires de Python en appliquant la théorie exposée ci-dessus. Votre programme devra donc : a) Demander à l'utilisateur le nombre de valeurs à tirer au hasard à l'aide de la fonction
random(). Il serait intéressant que le programme propose un nombre par défaut (1000 par exemple). b) Demander à l'utilisateur en combien de fractions il souhaite partager l'intervalle des valeurs possibles (c'est-à-dire l'intervalle de 0 à 1). Ici aussi, il faudrait proposer un nombre de par défaut (5 fractions, par exemple). Vous pouvez également limiter le choix de l'utilisateur à un nombre compris entre 2 et le 1/10e du nombre de valeurs tirées au hasard. c) Construire une liste de N compteurs (N étant le nombre de fractions souhaitées). Chacun d'eux sera évidemment initialisé à zéro. d) Tirer au hasard toutes les valeurs demandées, à l'aide de la fonctionrandom(), et mémoriser ces valeurs dans une liste. e) Mettre en œuvre un parcours de la liste des valeurs tirées au hasard (boucle), et effectuer un test sur chacune d'elles pour déterminer dans quelle fraction de l'intervalle 0-1 elle se situe. Incrémenter de une unité le compteur correspondant. f) Lorsque c'est terminé, afficher l'état de chacun des compteurs.
Solution
-
#(Test du générateur de nombres aléatoires, page 142) : from random import random # tire au hasard un réel entre 0 et 1 n = raw_input("Nombre de valeurs à tirer au hasard (défaut = 1000) : ") if n == "": nVal =1000 else: nVal = int(n) n = raw_input("Nombre de fractions dans l'intervalle 0-1 (entre 2 et " + str(nVal/10) + ", défaut =5) : ") if n == "": nFra =5 else: nFra = int(n) if nFra < 2: nFra =2 elif nFra > nVal/10: nFra = nVal/10 print "Tirage au sort des", nVal, "valeurs ..." listVal = [0]*nVal # créer une liste de zéros for i in range(nVal): # puis modifier chaque élément listVal[i] = random() print "Comptage des valeurs dans chacune des", nFra, "fractions ..." listCompt = [0]*nFra # créer une liste de compteurs # parcourir la liste des valeurs : for valeur in listVal: # trouver l'index de la fraction qui contient la valeur : index = int(valeur*nFra) # incrémenter le compteur correspondant : listCompt[index] = listCompt[index] +1 # afficher l'état des compteurs : for compt in listCompt: print compt,
[modifier] Exemple de résultats affichés par un programme de ce type :
Nombre de valeurs à tirer au hasard (défaut = 1000) : 100 Nombre de fractions dans l'intervalle 0-1 (entre 2 et 10, défaut =5) : 5 Tirage au sort des 100 valeurs ... Comptage des valeurs dans chacune des 5 fractions ... 11 30 25 14 20 Nombre de valeurs à tirer au hasard (défaut = 1000) : 10000 Nombre de fractions dans l'intervalle 0-1 (entre 2 et 1000, défaut =5) : 5 Tirage au sort des 10000 valeurs ... Comptage des valeurs dans chacune des 5 fractions ... 1970 1972 2061 1935 2062
Une bonne approche de ce genre de problème consiste à essayer d'imaginer quelles fonctions simples vous pourriez écrire pour résoudre l'une ou l'autre partie du problème, puis de les utiliser dans un ensemble plus vaste.
Par exemple, vous pourriez chercher à définir d'abord une fonction numeroFraction() qui servirait à déterminer dans quelle fraction de l'intervalle 0-1 une valeur tirée se situe. Cette fonction attendrait deux arguments (la valeur tirée, le nombre de fractions choisi par l'utilisateur) et fournirait en retour l'index du compteur à incrémenter (c'est-à-dire le n° de la fraction corres-pondante). Il existe peut-être un raisonnement mathématique simple qui permette de déterminer l'index de la fraction à partir de ces deux arguments. Pensez notamment à la fonction intégrée int(), qui permet de convertir un nombre réel en nombre entier en éliminant sa partie décimale.
Si vous ne trouvez pas, une autre réflexion intéressante serait peut-être de construire d'abord une liste contenant les valeurs « pivots » qui délimitent les fractions retenues (par exemple 0 – 0,25 – 0,5 – 0,75 - 1 dans le cas de 4 fractions). La connaissance de ces valeurs faciliterait peut-être l'écriture de la fonction numeroFraction() que nous souhaitons mettre au point.
Si vous disposez d'un temps suffisant, vous pouvez aussi réaliser une version graphique de ce programme, qui présentera les résultats sous la forme d'un histogramme (diagramme « en bâtons »).
[modifier] Tirage au hasard de nombres entiers
Lorsque vous développerez des projets personnels, il vous arrivera fréquemment de souhaiter pouvoir disposer d'une fonction qui permette de tirer au hasard un nombre entier entre certaines limites. Par exemple, si vous voulez écrire un programme de jeu dans lequel des cartes à jouer sont tirées au hasard (à partir d'un jeu ordinaire de 52 cartes), vous aurez certainement l'utilité d'une fonction capable de tirer au hasard un nombre entier compris entre 1 et 52.
Vous pouvez pour ce faire utiliser la fonction randrange() du module random.
Cette fonction peut être utilisée avec 1, 2 ou 3 arguments.
Avec un seul argument, elle renvoie un entier compris entre zéro et la valeur de l'argument diminué d'une unité. Par exemple, randrange(6) renvoie un nombre compris entre 0 et 5.
Avec deux arguments, le nombre renvoyé est compris entre la valeur du premier argument et la valeur du second argument diminué d'une unité. Par exemple, randrange(2, 8) renvoie un nombre compris entre 2 et 7.
Si l'on ajoute un troisième argument, celui-ci indique que le nombre tiré au hasard doit faire partie d'une série limitée d'entiers, séparés les uns des autres par un certain intervalle, défini lui-même par ce troisième argument. Par exemple, randrange(3, 13, 3) renverra un des nombres de la série 3, 6, 9, 12 :
>>> for i in range(15):
print random.randrange(3,13,3),
3 12 6 9 6 6 12 6 3 6 9 3 6 12 12
Exercices
- Ecrivez un script qui tire au hasard des cartes à jouer. Le nom de la carte tirée doit être correctement présenté, « en clair ». Le programme affichera par exemple :
Frappez <Enter> pour tirer une carte :
Dix de Trèfle
Frappez <Enter> pour tirer une carte :
As de Carreau
Frappez <Enter> pour tirer une carte :
Huit de Pique
Frappez <Enter> pour tirer une carte :
etc ...
Solution
-
# tirage de cartes from random import randrange couleurs = ['Pique', 'Trèfle', 'Carreau', 'Coeur'] valeurs = [2, 3, 4, 5, 6, 7, 8, 9, 10, 'valet', 'dame', 'roi', 'as'] # Construction de la liste des 52 cartes : carte =[] for coul in couleurs: for val in valeurs: carte.append("%s de %s" % (str(val), coul)) # Tirage au hasard : while 1: k = raw_input("Frappez <c> pour tirer une carte, <Enter> pour terminer ") if k =="": break r = randrange(52) print carte[r]
[modifier] Les tuples
Nous avons étudié jusqu'ici deux types de données composites : les chaînes, qui sont composées de caractères, et les listes, qui sont composées d'éléments de n'importe quel type. Vous devez vous rappeler une autre différence importante entre chaînes et listes : il n'est pas possible de changer les caractères au sein d'une chaîne existante, alors que vous pouvez modifier les éléments d'une liste. En d'autres termes, les listes sont des séquences modifiables, alors que les chaînes sont des séquences non-modifiables. Exemple :
>>> liste =['jambon','fromage','miel','confiture','chocolat']
>>> liste[1:3] =['salade']
>>> print liste
['jambon', 'salade', 'confiture', 'chocolat']
>>> chaine ='Roméo préfère Juliette'
>>> chaine[14:] ='Brigitte'
***** ==> Erreur: object doesn't support slice assignment *****
Nous essayons de modifier la fin de la chaîne, mais cela ne marche pas. La seule possibilité d'arriver à nos fins est de créer une nouvelle chaîne et d'y recopier ce que nous voulons changer :
>>> chaine = chaine[:14] +'Brigitte' >>> print chaine Roméo préfère Brigitte
Python propose un type de données appelé tuple[3], qui est assez semblable à une liste mais qui n'est pas modifiable. Du point de vue de la syntaxe, un tuple est une collection d'éléments séparés par des virgules :
>>> tuple = 'a', 'b', 'c', 'd', 'e'
>>> print tuple
('a', 'b', 'c', 'd', 'e')
Bien que cela ne soit pas nécessaire, il est vivement conseillé de mettre le tuple en évidence en l'enfermant dans une paire de parenthèses, comme l'instruction print de Python le fait elle-même. (Il s'agit simplement d'améliorer la lisibilité du code, mais vous savez que c'est important) :
>>> tuple = ('a', 'b', 'c', 'd', 'e')
Les opérations que l'on peut effectuer sur des tuples sont syntaxiquement similaires à celles que l'on effectue sur les listes, si ce n'est que les tuples ne sont pas modifiables :
>>> print tuple[2:4]
('c', 'd')
>>> tuple[1:3] = ('x', 'y') ==> ***** erreur *****
>>> tuple = ('André',) + tuple[1:]
>>> print tuple
('André', 'b', 'c', 'd', 'e')
Remarquez qu'il faut toujours au moins une virgule pour définir un tuple (le dernier exemple ci-dessus utilise un tuple contenant un seul élément : 'André'). Vous comprendrez l'utilité des tuples petit à petit. Signalons simplement ici qu'ils sont préférables aux listes partout où l'on veut être certain que les données transmises ne soient pas modifiées par erreur au sein d'un programme. En outre, les tuples sont moins « gourmands » en ressources système (ils occupent moins de place en mémoire).
[modifier] Les dictionnaires
Les types composites que nous avons abordés jusqu'à présent (chaînes, listes et tuples) étaient tous des séquences, c'est-à-dire des suites ordonnées d'éléments. Dans une séquence, il est facile d'accéder à un élément quelconque à l'aide d'un index (un nombre entier), mais à la condition expresse de connaître son emplacement.
Les dictionnaires que nous découvrons ici constituent un autre type composite. Ils ressemblent aux listes dans une certaine mesure (ils sont modifiables comme elles), mais ce ne sont pas des séquences. Les éléments que nous allons y enregistrer ne seront pas disposés dans un ordre immuable. En revanche, nous pourrons accéder à n'importe lequel d'entre eux à l'aide d'un index spécifique que l'on appellera une clé, laquelle pourra être alphabétique, numérique, ou même d'un type composite sous certaines conditions.
[modifier] Création d'un dictionnaire
A titre d'exemple, nous allons créer un dictionnaire de langue, pour la traduction de termes informatiques anglais en français. Dans ce dictionnaire, les index seront des chaînes de caractères.
Puisque le type dictionnaire est un type modifiable, nous pouvons commencer par créer un dictionnaire vide, puis le remplir petit à petit. Du point de vue syntaxique, on reconnaît une structure de données de type dictionnaire au fait que ses éléments sont enfermés dans une paire d'accolades. Un dictionnaire vide sera donc noté { } :
>>> dico = {}
>>> dico['computer'] = 'ordinateur'
>>> dico['mouse'] ='souris'
>>> dico['keyboard'] ='clavier'
>>> print dico
{'computer': 'ordinateur', 'keyboard': 'clavier', 'mouse': 'souris'}
Comme vous pouvez l'observer dans la ligne ci-dessus, un dictionnaire apparaît comme une série d'éléments séparés par des virgules (le tout étant enfermé entre deux accolades}. Chacun de ces éléments est constitué d'une paire d'objets : un index et une valeur, séparés par un double point.
Dans un dictionnaire, les index s'appellent des clés, et les éléments peuvent donc s'appeler des paires clé-valeur. Vous pouvez constater que l'ordre dans lequel les éléments apparaissent à la dernière ligne ne correspond pas à celui dans lequel nous les avons fournis. Cela n'a strictement aucune importance : nous n'essaierons jamais d'extraire une valeur d'un dictionnaire à l'aide d'un index numérique. Au lieu de cela, nous utiliserons les clés :
>>> print dico['mouse'] souris
Contrairement à ce qui se passe avec les listes, il n'est pas nécessaire de faire appel à une méthode particulière (telle que append()) pour ajouter de nouveaux éléments à un dictionnaire : il suffit de créer une nouvelle paire clé-valeur.
[modifier] Opérations sur les dictionnaires
Vous savez déjà comment ajouter des éléments à un dictionnaire. Pour en enlever, vous utiliserez l'instruction del. Créons pour l'exemple un autre dictionnaire, destiné cette fois à contenir l'inventaire d'un stock de fruits. Les index (ou clés) seront les noms des fruits, et les valeurs seront les masses de ces fruits répertoriées dans le stock (il s'agit donc cette fois de valeurs de type numérique).
>>> invent = {'pommes': 430, 'bananes': 312, 'oranges' : 274, 'poires' : 137}
>>> print invent
{'oranges': 274, 'pommes': 430, 'bananes': 312, 'poires': 137}
Si le patron décide de liquider toutes les pommes et de ne plus en vendre, nous pouvons enlever cette entrée dans le dictionnaire :
>>> del invent['pommes']
>>> print invent
{'oranges': 274, 'bananes': 312, 'poires': 137}
La fonction len() est utilisable avec un dictionnaire : elle en renvoie le nombre d'éléments.
[modifier] Les dictionnaires sont des objets
On peut appliquer aux dictionnaires un certain nombre de méthodes spécifiques :
La méthode keys() renvoie la liste des clés utilisées dans le dictionnaire :
>>> print dico.keys() ['computer', 'keyboard', 'mouse']
La méthode values() renvoie la liste des valeurs mémorisées dans le dictionnaire :
>>> print invent.values() [274, 312, 137]
La méthode has_key() permet de savoir si un dictionnaire comprend une clé déterminée. On fournit la clé en argument, et la méthode renvoie une valeur 'vraie' ou 'fausse' (en fait, 1 ou 0), suivant que la clé est présente ou pas :
>>> print invent.has_key('bananes')
1
>>> if invent.has_key('pommes'):
print 'nous avons des pommes'
else:
print 'pas de pommes, sorry'
pas de pommes, sorry
La méthode items() extrait du dictionnaire une liste équivalente de tuples :
>>> print invent.items()
[('oranges', 274), ('bananes', 312), ('poires', 137)]
La méthode copy() permet d'effectuer une vraie copie d'un dictionnaire. Il faut savoir en effet que la simple affectation d'un dictionnaire existant à une nouvelle variable crée seulement une nouvelle référence vers le même objet, et non un nouvel objet. Nous avons déjà discuté ce phénomène (aliasing) à propos des listes (voir page 139). Par exemple, l'instruction ci-dessous ne définit pas un nouveau dictionnaire (contrairement aux apparences) :
>>> stock = invent
>>> print stock
{'oranges': 274, 'bananes': 312, 'poires': 137}
Si nous modifions invent, alors stock aussi est modifié, et vice-versa (ces deux noms désignent en effet le même objet dictionnaire dans la mémoire de l'ordinateur) :
>>> del invent['bananes']
>>> print stock
{'oranges': 274, 'poires': 137}
Pour obtenir une vraie copie (indépendante) d'un dictionnaire préexistant, il faut employer la méthode copy() :
>>> magasin = stock.copy()
>>> magasin['prunes'] = 561
>>> print magasin
{'oranges': 274, 'prunes': 561, 'poires': 137}
>>> print stock
{'oranges': 274, 'poires': 137}
>>> print invent
{'oranges': 274, 'poires': 137}
[modifier] Parcours d'un dictionnaire
Vous pouvez utiliser une boucle for pour traiter successivement tous les éléments contenus dans un dictionnaire, mais attention :
- Au cours de l'itération, ce sont les clés utilisées dans le dictionnaire qui seront successivement affectées à la variable de travail, et non les valeurs.
- L'ordre dans lequel les éléments seront extraits est imprévisible (puisqu'un dictionnaire n'est pas une séquence).
Exemple :
>>> invent ={"oranges":274, "poires":137, "bananes":312}
>>> for clef in invent:
... print clef
poires
bananes
oranges
Si vous souhaitez effectuer un traitement sur les valeurs, il vous suffit alors de récupérer chacune d'elles à partir de la clé correspondante :
for clef in invent:
print clef, invent[clef]
poires 137
bananes 312
oranges 274
Cette manière de procéder n'est cependant pas idéale, ni en termes de performances ni même du point de vue de la lisibilité. Il est recommandé de plutôt faire appel à la méthode items() décrite à la section précédente :
for clef, valeur in invent.items():
print clef, valeur
poires 137
bananes 312
oranges 274
[modifier] Les clés ne sont pas nécessairement des chaînes de caractères
Jusqu'à présent nous avons décrit des dictionnaires dont les clés étaient à chaque fois des valeurs de type string. En fait nous pouvons utiliser en guise de clés n'importe quel type de donnée non modifiable : des entiers, des réels, des chaînes de caractères, et même des tuples.
Considérons par exemple que nous voulions répertorier les arbres remarquables situés dans un grand terrain rectangulaire. Nous pouvons pour cela utiliser un dictionnaire, dont les clés seront des tuples indiquant les coordonnées x,y de chaque arbre :
>>> arb = {}
>>> arb[(1,2)] = 'Peuplier'
>>> arb[(3,4)] = 'Platane'
>>> arb[6,5] = 'Palmier'
>>> arb[5,1] = 'Cycas'
>>> arb[7,3] = 'Sapin'
>>> print arb
{(3, 4): 'Platane', (6, 5): 'Palmier', (5, 1): 'Cycas', (1, 2): 'Peuplier', (7, 3): 'Sapin'}
>>> print arb[(6,5)]
palmier
Vous pouvez remarquer que nous avons allégé l'écriture à partir de la troisième ligne, en profitant du fait que les parenthèses délimitant les tuples sont facultatives (à utiliser avec prudence !).
Dans ce genre de construction, il faut garder à l'esprit que le dictionnaire contient des éléments seulement pour certains couples de coordonnées. Ailleurs, il n'y a rien. Par conséquent, si nous voulons interroger le dictionnaire pour savoir ce qui se trouve là où il n'y a rien, comme par exemple aux coordonnées (2,1), nous allons provoquer une erreur :
>>> print arb[1,2]
Peuplier
>>> print arb[2,1]
***** Erreur : KeyError: (2, 1) *****
Pour résoudre ce petit problème, nous pouvons utiliser la méthode get() :
>>> print arb.get((1,2),'néant') Peuplier >>> print arb.get((2,1),'néant') néant
Le premier argument transmis à cette méthode est la clé de recherche, le second argument est la valeur que nous voulons obtenir en retour si la clé n'existe pas dans le dictionnaire.
[modifier] Les dictionnaires ne sont pas des séquences
Comme vous l'avez vu plus haut, les éléments d'un dictionnaire ne sont pas disposés dans un ordre particulier. Des opérations comme la concaténation et l'extraction (d'un groupe d'éléments contigus) ne peuvent donc tout simplement pas s'appliquer ici. Si vous essayez tout de même, Python lèvera une erreur lors de l'exécution du code :
>>> print arb[1:3]
***** Erreur : KeyError: slice(1, 3, None) *****
Vous avez vu également qu'il suffit d'affecter un nouvel indice (une nouvelle clé) pour ajouter une entrée au dictionnaire. Cela ne marcherait pas avec les listes[4] :
>>> invent['cerises'] = 987
>>> print invent
{'oranges': 274, 'cerises': 987, 'poires': 137}
>>> liste =['jambon', 'salade', 'confiture', 'chocolat']
>>> liste[4] ='salami'
***** IndexError: list assignment index out of range *****
Du fait qu'ils ne sont pas des séquences, les dictionnaires se révèlent donc particulièrement précieux pour gérer des ensembles de données où l'on est amené à effectuer fréquemment des ajouts ou des suppressions, dans n'importe quel ordre. Ils remplacent avantageusement les listes lorsqu'il s'agit de traiter des ensembles de données numérotées, dont les numéros ne se suivent pas.
Exemple :
>>> client = {}
>>> client[4317] = "Dupond"
>>> client[256] = "Durand"
>>> client[782] = "Schmidt"
etc.
Exercices
- Écrivez un script qui crée un mini-système de base de données fonctionnant à l'aide d'un dictionnaire, et dans lequel vous mémoriserez les noms d'une série de copains, leur âge et leur taille. Votre script devra comporter deux fonctions : la première pour le remplissage du dictionnaire, et la seconde pour sa consultation. Dans la fonction de remplissage, utilisez une boucle pour accepter les données entrées par l'utilisateur. Dans le dictionnaire, le nom de l'élève servira de clé d'accès, et les valeurs seront constituées de tuples (âge, taille), dans lesquels l'âge sera exprimé en années (donnée de type entier), et la taille en mètres (donnée de type réel). La fonction de consultation comportera elle aussi une boucle, dans laquelle l'utilisateur pourra fournir un nom quelconque pour obtenir en retour le couple « âge, taille » correspondant. Le résultat de la requête devra être une ligne de texte bien formatée, telle par exemple : « Nom : Jean Dhoute - âge : 15 ans - taille : 1.74 m ». Pour obtenir ce résultat, servez-vous du formatage des chaînes de caractères décrit à la page todo.
- Écrivez une fonction qui échange les clés et les valeurs d'un dictionnaire (ce qui permettra par exemple de transformer un dictionnaire anglais/français en un dictionnaire français/anglais). (On suppose que le dictionnaire ne contient pas plusieurs valeurs identiques).
Solution
-
#Création et consultation d'un dictionnaire def consultation(): while 1: nom = raw_input("Entrez le nom (ou <enter> pour terminer) : ") if nom == "": break if dico.has_key(nom): # le nom est-il répertorié ? item = dico[nom] # consultaion proprement dite age, taille = item[0], item[1] print "Nom : %s - âge : %s ans - taille : %s m."\ % (nom, age, taille) else: print "*** nom inconnu ! ***" def remplissage(): while 1: nom = raw_input("Entrez le nom (ou <enter> pour terminer) : ") if nom == "": break age = int(raw_input("Entrez l'âge (nombre entier !) : ")) taille = float(raw_input("Entrez la taille (en mètres) : ")) dico[nom] = (age, taille) dico ={} while 1: choix = raw_input("Choisissez : (R)emplir - (C)onsulter - (T)erminer : ") if choix.upper() == 'T': break elif choix.upper() == 'R': remplissage() elif choix.upper() == 'C': consultation() -
# échange des clés et des valeurs dans un dictionnaire def inverse(dico): "Construction d'un nouveau dico, pas à pas" dic_inv ={} for cle in dico: item = dico[cle] dic_inv[item] = cle return dic_inv # programme test : dico = {'Computer':'Ordinateur', 'Mouse':'Souris', 'Keyboard':'Clavier', 'Hard disk':'Disque dur', 'Screen':'Ecran'} print dico print inverse(dico)
[modifier] Construction d'un histogramme à l'aide d'un dictionnaire
Les dictionnaires constituent un outil très élégant pour construire des histogrammes.
Supposons par exemple que nous voulions établir l'histogramme qui représente la fréquence d'utilisation de chacune des lettres de l'alphabet dans un texte donné. L'algorithme permettant de réaliser ce travail est extraordinairement simple si on le construit sur base d'un dictionnaire :
>>> texte ="les saucisses et saucissons secs sont dans le saloir"
>>> lettres ={}
>>> for c in texte:
lettres[c] = lettres.get(c, 0) + 1
>>> print lettres
{'t': 2, 'u': 2, 'r': 1, 's': 14, 'n': 3, 'o': 3, 'l': 3, 'i': 3, 'd': 1,
'e': 5, 'c': 3, ' ': 8, 'a': 4}
Nous commençons par créer un dictionnaire vide : lettres. Ensuite, nous allons remplir ce dictionnaire en utilisant les caractères de l'alphabet en guise de clés. Les valeurs que nous mémoriserons pour chacune de ces clés seront les fréquences des caractères correspondants dans le texte. Afin de calculer celles-ci, nous effectuons un parcours de la chaîne de caractères texte. Pour chacun de ces caractères, nous interrogeons le dictionnaire à l'aide de la méthode get(), en utilisant le caractère en guise de clé, afin d'y lire la fréquence déjà mémorisée pour ce caractère. Si cette valeur n'existe pas encore, la méthode get() doit renvoyer une valeur nulle. Dans tous les cas, nous incrémentons la valeur trouvée, et nous la mémorisons dans le dictionnaire à l'emplacement qui correspond à la clé (c'est-à-dire au caractère en cours de traitement).
Pour fignoler notre travail, nous pouvons encore souhaiter afficher l'histogramme dans l'ordre alphabétique. Pour ce faire, nous pensons immédiatement à la méthode sort(), mais celle-ci ne peut s'appliquer qu'aux listes. Qu'à cela ne tienne ! Nous avons vu plus haut comment nous pouvions convertir un dictionnaire en une liste de tuples :
>>> lettres_triees = lettres.items()
>>> lettres_triees.sort()
>>> print lettres_triees
[(' ', 8), ('a', 4), ('c', 3), ('d', 1), ('e', 5), ('i', 3), ('l', 3),
('n', 3), ('o', 3), ('r', 1), ('s', 14), ('t', 2), ('u', 2)]
Exercices
- Vous avez à votre disposition un fichier texte quelconque (pas trop gros). Écrivez un script qui compte les occurrences de chacune des lettres de l'alphabet dans ce texte (on ne tiendra pas compte du problème des lettres accentuées)..
- Modifiez le script ci-dessus afin qu'il établisse une table des occurrences de chaque mot dans le texte. Conseil : dans un texte quelconque, les mots ne sont pas seulement séparés par des espaces, mais également par divers signes de ponctuation. Pour simplifier le problème, vous pouvez commencer par remplacer tous les caractères non-alphabétiques par des espaces, et convertir la chaîne résultante en une liste de mots à l'aide de la méthode
split(). - Vous avez à votre disposition un fichier texte quelconque (pas trop gros). Écrivez un script qui analyse ce texte, et mémorise dans un dictionnaire l'emplacement exact de chacun des mots (compté en nombre de caractères à partir du début). Lorsqu'un même mot apparaît plusieurs fois, tous ses emplacements doivent être mémorisés : chaque valeur de votre dictionnaire doit donc être une liste d'emplacements.
Solution
-
# histogramme nFich = raw_input('Nom du fichier : ') fi = open(nFich, 'r') texte = fi.read() # conversion du fichier en une chaîne de caractères fi.close() print texte dico ={} for c in texte: c = c.upper() # conversion de toutes les lettres en majuscules dico[c] = dico.get(c, 0) +1 liste = dico.items() liste.sort() print liste -
nFich = raw_input('Nom du fichier à traiter : ') fi = open(nFich, 'r') texte = fi.read() fi.close() # afin de pouvoir aisément séparer les mots du texte, on commence # par convertir tous les caractères non-alphabétiques en espaces : alpha = "abcdefghijklmnopqrstuvwxyzéèàùçâêîôûäëïöü" lettres = '' # nouvelle chaîne à construire for c in texte: c = c.lower() # conversion de chaque caractère en minuscule if c in alpha: lettres = lettres + c else: lettres = lettres + ' ' # conversion de la chaîne résultante en une liste de mots : mots = lettres.split() # construction de l'histogramme : dico ={} for m in mots: dico[m] = dico.get(m, 0) +1 liste = dico.items() # tri de la liste résultante : liste.sort() # affichage en clair : for item in liste: print item[0], ":", item[1] -
# encodage d'un texte dans un dictionnaire nFich = raw_input('Nom du fichier à traiter : ') fi = open(nFich, 'r') texte = fi.read() fi.close() # On considère que les mots sont des suites de caractères faisant partie # de la chaîne ci-dessous. Tous les autres sont des séparateurs : alpha = "abcdefghijklmnopqrstuvwxyzéèàùçâêîôûäëïöü" # construction du dictionnaire : dico ={} # parcours de tous les caractères du texte : i =0 # indice du caractère en cours de lecture mot ="" # variable de travail : mot en cours de lecture for c in texte: c = c.lower() # conversion de chaque caractère en minuscule if c in alpha: # car. alphab. => on est à l'intérieur d'un mot mot = mot + c else: # car. non-alphabétique => fin de mot if mot != "": # afin d'ignorer les car. non-alphab. successifs # pour chaque mot, on construit une liste d'indices : if dico.has_key(mot): # mot déjà répertorié : dico[mot].append(i) # ajout d'un indice à la liste else: # mot rencontré pour la 1e fois : dico[mot] =[i] # création de la liste d'indices mot ="" # préparer la lecture du mot suivant i = i+1 # indice du caractère suivant # Affichage du dictionnaire, en clair : for clef, valeur in dico.items(): print clef, ":", valeur
[modifier] Contrôle du flux d'exécution à l'aide d'un dictionnaire
Il arrive fréquemment que l'on ait à diriger l'exécution d'un programme dans différentes directions, en fonction de la valeur prise par une variable. Vous pouvez bien évidemment traiter ce problème à l'aide d'une série d'instructions if - elif - else , mais cela peut devenir assez lourd et inélégant si vous avez affaire à un grand nombre de possibilités. Exemple :
materiau = raw_input("Choisissez le matériau : ")
if materiau == 'fer':
fonctionA()
elif materiau == 'bois':
fonctionC()
elif materiau == 'cuivre':
fonctionB()
elif materiau == 'pierre':
fonctionD()
elif ... etc ...
Les langages de programmation proposent souvent des instructions spécifiques pour traiter ce genre de problème, telles les instructions switch ou case du C ou du Pascal. Python n'en propose aucune, mais vous pouvez vous tirer d'affaire dans bien des cas à l'aide d'une liste (nous en donnons un exemple détaillé page 227), ou mieux encore à l'aide d'un dictionnaire. Exemple :
materiau = raw_input("Choisissez le matériau : ")
dico = {'fer':fonctionA,
'bois:fonctionC,
'cuivre':fonctionB,
'pierre':fonctionD,
... etc ...}
dico[materiau]()
Les deux instructions ci-dessus pourraient être condensées en une seule, mais nous les laissons séparées pour bien détailler le mécanisme :
- La première instruction définit un dictionnaire dico dans lequel les clés sont les différentes possibilités pour la variable
materiau, et les valeurs, les noms des fonctions à invoquer en correspondance. Notez bien qu'il s'agit seulement des noms de ces fonctions, qu'il ne faut surtout pas faire suivre de parenthèses dans ce cas (Sinon Python exécuterait chacune de ces fonctions au moment de la création du dictionnaire).
- La seconde instruction invoque la fonction correspondant au choix opéré à l'aide de la variable
materiau. Le nom de la fonction est extrait du dictionnaire à l'aide de la clé, puis associé à une paire de parenthèses. Python reconnaît alors un appel de fonction tout à fait classique et l'exécute.
Vous pouvez encore améliorer la technique ci-dessus en remplaçant cette instruction par sa variante ci-dessous, qui fait appel à la méthode get() afin de prévoir le cas où la clé demandée n'existerait pas dans le dictionnaire (vous obtenez de cette façon l'équivalent d'une instruction else terminant une longue série de elif) :
dico.get(materiau, fonctAutre)()
Lorsque la la valeur de la variable materiau ne correspond à aucune clé du dictionnaire, c'est la fonction fonctAutre() qui est invoquée.
Exercices
- Complétez l'exercice
(mini-système de base de données) en lui ajoutant deux fonctions : l'une pour enregistrer le dictionnaire résultant dans un fichier texte, et l'autre pour reconstituer ce dictionnaire à partir du fichier correspondant.
Chaque ligne de votre fichier texte correspondra à un élément du dictionnaire. Elle sera formatée de manière à bien séparer :
- la clé et la valeur (c'est-à-dire le nom de la personne, d'une part, et l'ensemble : « âge + taille », d'autre part.
- dans l'ensemble « âge + taille », ces deux données numériques.
Vous utiliserez donc deux caractères séparateurs différents, par exemple « @ » pour séparer la clé et la valeur, et « # » pour séparer les données constituant cette valeur :
Juliette@18#1.67 Jean-Pierre@17#1.78 Delphine@19#1.71 Anne-Marie@17#1.63 etc.
- Améliorez encore le script de l'exercice précédent, en utilisant un dictionnaire pour diriger le flux d'exécution du programme au niveau du menu principal. Votre programme affichera par exemple :
Choisissez : (R)écupérer un dictionnaire préexistant sauvegardé dans un fichier (A)jouter des données au dictionnaire courant (C)onsulter le dictionnaire courant (S)auvegarder le dictionnaire courant dans un fichier (T)erminer :
Suivant le choix opéré par l'utilisateur, vous effectuerez alors l'appel de la fonction correspondante en la sélectionnant dans un dictionnaire de fonctions.
Solution
-
Sauvegarde d'un dictionnaire (complément de l'ex. 10.45). def enregistrement(): fich = raw_input("Entrez le nom du fichier de sauvegarde : ") ofi = open(fich, "w") # parcours du dictionnaire entier, converti au préalable en une liste : for cle, valeur in dico.items(): # utilisation du formatage des chaînes pour créer l'enregistrement : ofi.write("%s@%s#%s\n" % (cle, valeur[0], valeur[1])) ofi.close() def lectureFichier(): fich = raw_input("Entrez le nom du fichier de sauvegarde : ") try: ofi = open(fich, "r") except: print "*** fichier inexistant ***" return while 1: ligne = ofi.readline() if ligne =='': # détection de la fin de fichier break enreg = ligne.split("@") # restitution d'une liste [clé,valeur] cle = enreg[0] valeur = enreg[1][:-1] # élimination du caractère de fin de ligne data = valeur.split("#") # restitution d'une liste [âge, taille] age, taille = int(data[0]), float(data[1]) dico[cle] = (age, taille) # reconstitution du dictionnaire ofi.close()Ces deux fonctions peuvent être appelées respectivement à la fin et au début du programme principal, comme dans l'exemple ci-dessous :
dico ={} lectureFichier() while 1: choix = raw_input("Choisissez : (R)emplir - (C)onsulter - (T)erminer : ") if choix.upper() == 'T': break elif choix.upper() == 'R': remplissage() elif choix.upper() == 'C': consultation() enregistrement() - Cet exercice complète le précédent. On ajoute encore deux petites fonctions, et on réécrit le corps principal du programme pour diriger le flux d'exécution en se servant d'un dictionnaire :
# Contrôle du flux d'exécution à l'aide d'un dictionnaire def sortie(): print "*** Job terminé ***" return 1 # afin de provoquer la sortie de la boucle def autre(): print "Veuillez frapper R, A, C, S ou T, svp." dico ={} fonc ={"R":lectureFichier, "A":remplissage, "C":consultation, "S":enregistrement, "T":sortie} while 1: choix = raw_input("Choisissez :\n" +\ "(R)écupérer un dictionnaire préexistant sauvegardé dans un fichier\n" +\ "(A)jouter des données au dictionnaire courant\n" +\ "(C)onsulter le dictionnaire courant\n" +\ "(S)auvegarder le dictionnaire courant dans un fichier\n" +\ "(T)erminer : ") # l'instruction ci-dessous appelle une fonction différente pour # chaque choix, par l'intermédiaire du dictionnaire <fonc> : if fonc.get(choix, autre)(): break # Rem : toutes les fonctions appelées ici renvoient <None> par défaut, # sauf la fonction sortie() qui renvoie 1 => sortie de la boucle
[modifier] Notes
- ↑ En fait, il existe plusieurs systèmes de codage : les plus connus sont les codages ASCII et ANSI, assez proches l'un de l'autre sauf en ce qui concerne les caractères particuliers spécifiques des langues autres que l'anglais (caractères accentués, cédilles, etc.). Un nouveau système de codage intégrant tous les caractères spéciaux de toutes les langues mondiales est apparu depuis quelques années. Ce système appelé unicode devrait s'imposer petit à petit. Python l'intègre à partir de sa version 2.
- ↑ Il s'agit de quelques exemples seulement. La plupart de ces méthodes peuvent être utilisées avec différents paramètres que nous n'indiquons pas tous ici (par exemple, certains paramètres permettent de ne traiter qu'une partie de la chaîne). Vous pouvez obtenir la liste complète de toutes les méthodes associées à un objet à l'aide de la fonction intégrée
dir(). Veuillez consulter l'un ou l'autre des ouvrages de référence (ou la documentation en ligne) si vous souhaitez en savoir davantage. - ↑ ce terme n'est pas un mot anglais : il s'agit d'un néologisme informatique
- ↑ Rappel : les méthodes permettant d'ajouter des éléments à une liste sont décrites page .

