Programmation Python/Dictionnaires

Un livre de Wikilivres.
Aller à : navigation, rechercher


Déclarations[modifier | modifier le wikicode]

Le dictionnaire ou tableau associatif n'est pas une séquence mais un autre type composite. Ils ressemblent aux listes dans une certaine mesure (ils sont modifiables comme elles), mais 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.

 comme dans une liste, les éléments mémorisés dans un dictionnaire peuvent être de n'importe quel type (valeurs numériques, chaînes, listes, etc.).

Création d'un dictionnaire[modifier | modifier le wikicode]

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 pour ajouter de nouveaux éléments à un dictionnaire : il suffit de créer une nouvelle paire clé-valeur.

Opérations sur les dictionnaires[modifier | modifier le wikicode]

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.

Les dictionnaires sont des objets[modifier | modifier le wikicode]

On peut appliquer aux dictionnaires un certain nombre de méthodes spécifiques :

keys et values[modifier | modifier le wikicode]

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]

has_key[modifier | modifier le wikicode]

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

items[modifier | modifier le wikicode]

La méthode items() extrait du dictionnaire une liste équivalente de tuples :

>>> print invent.items()
[('oranges', 274), ('bananes', 312), ('poires', 137)]

Il est donc utilisé pour les "foreach" :

for key, value in invent.items():
    print "Clé : %s, valeur : %s." % (key, value)

copy[modifier | modifier le wikicode]

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 (aliasing). 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}

>>> del invent['bananes']
>>> print stock
{'oranges': 274, '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).

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}

update[modifier | modifier le wikicode]

Il est possible de concaténer deux dictionnaires avec cette méthode :

>>> d = {'apples': 1, 'oranges': 3, 'pears': 2}
>>> ud = {'pears': 4, 'grapes': 5, 'lemons': 6}
>>> d.update(ud)
>>> d
{'grapes': 5, 'pears': 4, 'lemons': 6, 'apples': 1, 'oranges': 3}
>>>
 les doublons sont automatiquement fusionnés.

Parcours d'un dictionnaire[modifier | modifier le wikicode]

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

Les clés ne sont pas nécessairement des chaînes de caractères[modifier | modifier le wikicode]

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
foret disposée en grille dont les arbres sont générés par un dictionnaire

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.

Les dictionnaires ne sont pas des séquences[modifier | modifier le wikicode]

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 :

>>> 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

  1. É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 ».
  2. É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

  1. #!/usr/bin/env python
    # coding: utf-8
    
    # Création du dictionnaire
    dico ={}
    while 1:
        choix = raw_input("Choisissez : (R)emplir - (C)onsulter - (T)erminer : ")
        if choix.upper() == 'T':
            break
    
        elif choix.upper() == 'R':
    	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)
    
        elif choix.upper() == 'C':
    	# Consultation
    	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]            # consultation proprement dite
    		age, taille = item[0], item[1]
    		print "Nom : %s - âge : %s ans - taille : %s m."\
    			   % (nom, age, taille)          
    	else:
    		print "*** nom inconnu ! ***"
    
  2. # Échange des clés et des valeurs dans un dictionnaire
    dico = {'Computer':'Ordinateur',
            'Mouse':'Souris',
            'Keyboard':'Clavier',
            'Hard disk':'Disque dur',
            'Screen':'Ecran'}
    
    print(dico)
    
    dic_inv ={} 
    for cle in dico:
    	item = dico[cle]  
    	dic_inv[item] = cle
    
    print(dic_inv)
    

Construction d'un histogramme à l'aide d'un dictionnaire[modifier | modifier le wikicode]

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

  1. 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)..
  2. 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().
  3. 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

  1. # 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
    
  2. 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]
    
  3. # 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étiques 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
    

Contrôle du flux d'exécution à l'aide d'un dictionnaire[modifier | modifier le wikicode]

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, 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.