18. Dictionnaires#

Pour la création de la première carte nous avons utilisé une liste de stations de métro où chaque station était en représenté par une liste. Pour mémoire, voici un extrait des données que nous manipulions.

[
 [48.80350127856915, 2.3174291790534154, 'Bagneux - Lucie Aubrac'],
 [48.80991722995029, 2.317686671122904, 'Barbara'],
 [48.81862126138922, 2.319703692309543, 'Mairie de Montrouge'],
 ...
]

Les listes, qui représentent les stations, contiennent trois valeurs : une latitude, une longitude et un nom. Ces données sont de nature différente, elles ne sont pas interchangeables. Lorsque nous avons manipulé ces données il a donc fallu faire attention aux indices utilisés. C’est fastidieux et surtout cela nous expose à des bugs.

Pour contourner ce problème, nous pouvons utiliser des dictionnaires (dict). Ce sont des strucures de données qui permettent de regrouper un grand nombre de valeurs identifiées par des « clefs ». Autrement dit, un dictionnaire est un ensemble d”associations clef-valeur.

18.1. Utilisation#

18.1.1. Création#

Voici comment créer un dictionnaire vide.

d = {}

Et voici un dictionnaire avec deux associations.

d = {"clef1": "valeur1", "clef2":"valeur2"}

Les valeurs contenues dans un dictionnaires peuvent être de nature quelconque, des nombres, des chaînes, des listes ou même d’autres dictionnaires.

Par contre, les clefs doivent être non mutables, c’est-à-dire qu’elles ne doivent pas pouvoir être modifiées. Un type non mutable est par exemple, un entier, un nombre à virgule, une chaîne ou un tuple (pourvu qu’il soit exclusivement composé de valeur non mutable).

18.1.2. Accès aux valeurs#

L’accès aux valeurs d’un dictionnaire est assez sembable à celle des séquences. On utilise la notion crochet.

d = {"a":1, "b":2, "c":3}
d["b"]
2

N’imaginez pas qu’accéder à une valeur dans un dictionnaire Python soit aussi laborieux que de chercher dans un dictionnaire de français. C’est aussi rapide que d’accéder à un élément dans une liste à partir de son indice.

Une erreur KeyError sera générée si l’on cherche à accéder à une clef qui n’existe pas dans un dictionnaire.

d["x"]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[2], line 1
----> 1 d["x"]

KeyError: 'x'

Pour éviter ce genre d’erreur Python propose une fonction get qui permet d’accéder à la valeur associée à un clef ou à une valeur par défaut si la clef n’est associé à aucune valeur.

d.get("b", 24) # b est associé à 2, donc on renvoie 2 et on ne s'intéresse pas à la valeur par défaut
2
d.get("x", 24) # x n'est associé à rien, donc par défaut, on renvoie la valeur 24
24

18.1.3. Ajout et suppression d’associations#

C’est toujours la notation crochets que l’on utilise pour ajouter de nouvelles associations à un dictionnaire.

d["d"] = 10
d
{'a': 1, 'b': 2, 'c': 3, 'd': 10}

Attention, une clef ne peut être présente qu’en un unique exemplaire. L’ajout d’une association avec une clef déjà présente écrasera l’ancienne. C’est la manière de « modifier » les valeurs stockées dans un dictionnaire.

d["d"] = 4
d
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

Pour supprimer une association du dictionnaire, on utilise le mot clef del.

del d["d"]
d
{'a': 1, 'b': 2, 'c': 3}

18.1.4. Exemple d’utilisation#

Avec seulement ces quelques informations, on peut reprendre le code pour générer la carte de métro et constater que cela nous simplie la comprenhésion. Dans la version initiale, on divisait chaque ligne du fichier texte pour en faire une liste de trois valeurs.

def split_line(l):
    return [e.strip() for e in l.split(",")]

L’utilisation d’une station était alors obscure. Il fallait se rappeler la donnée associée à un indice.

def add_marker(l, m):
    lat = l[0]
    lon = l[1]
    name = l[2]
    folium.Marker(
        location=(lat, lon),
        popup=name,
        icon=folium.Icon(color="blue"),
    ).add_to(m)

Avec un dictionnaire on doit toujours diviser les lignes mais on peut stocker les valeurs de manière plus pertinente.

def split_line_dict(l):
    station = [e.strip() for e in l.split(",")]
    return {"lat":station[0], "lon":station[1], "name":station[2]}

On donne du sens à ce que l’on manipule. Pas de risque de confondre la longitude et le nom de la station.

def add_marker_dict(l, m):
    folium.Marker(
        location=(l["lat"], l["lon"]),
        popup=l["name"],
        icon=folium.Icon(color="blue"),
    ).add_to(m)

18.1.5. Parcours d’un dictionnaire#

On peut utiliser un dictionnaire dans une boucle for. Dans ce cas, ce sont les clefs qui sont parcourues.

d = {"a":1, "b":2, "c":3, "d":4}

for k in d:
    print(k)
    
b
c
a
d

Attention ! Les clefs sont visités dans un ordre quelconque. Cet ordre n’est pas prévisible. Vous ne devez jamais présupposer l’odre dans lequel les clefs seront parcourues.

Pour parcourir les valeurs, il faut utiliser la fonction values.

for v in d.values():
    print(v)
    
3
1
2
4

Enfin, il est possible de parcourir les associations avec la fonction items.

for k, v in d.items():
    print(k, v)
    
c 3
a 1
b 2
d 4

Notons enfin que l’on peut connaitre le nombre d’associations présentes dans un dictionnaire avec la fonction len.

len(d)
4

18.2. Exercices#

Voici quelques petits exercices pour s’assurer votre compréhension des dictionnaires.

Question : Écrivez une fonction qui prend en entrée un dictionnaire et retourne un nouveau dictionnaire avec les clés et les valeurs inversées. Par exemple, si le dictionnaire d’entrée est {'a': 1, 'b': 2, 'c': 3}, le dictionnaire de sortie doit être {1: 'a', 2: 'b', 3: 'c'}.

def invert(d):
    pass

invert({'a': 1, 'b': 2, 'c': 3})

Voici une correction possible.

def invert(d):
    c = {}
    for k, v in d.items():
        c[v] = k
    return c

Question : Écrivez une fonction qui prend en entrée deux dictionnaires et retourne un nouveau dictionnaire qui contient tous les éléments des deux dictionnaires d’entrée.

def add(d1, d2):
    pass

a = {"a":2, "c":56, "t":0}
b = {"z":2, "x":-2}

c = add(a,b)


{'a': 2, 'c': 56, 't': 0, 'z': 2, 'x': -2}

Voici une correction possible.

def add(d1, d2):
    c = {}
    for k, v in d1.items():
        c[k] = v
    for k, v in d2.items():
        c[k] = v
    return c

Question : Regardez la solution proposée par le corrigé. Selon vous, add(a, b) est-il équivalent à add(b, a) ?

Voici une correction possible.

Non, on ajoute d’abord dans le nouveau dictionnaire tous les associations du premier paramètre puis celle du second. Donc les deux dictionnaires partagent des clés en commun qui ne sont pas associées à des même valeurs alors on observera un résultat différent.

Question : Adaptons notre fonction pour que lorsqu’une clé est présente dans les deux dictionnaires, la valeur du nouveau dictionnaire doit être la somme des valeurs des deux dictionnaires. Par exemple, si les dictionnaires d’entrée sont {'a': 1, 'b': 2} et {'b': 3, 'c': 4}, le dictionnaire de sortie doit être {'a': 1, 'b': 5, 'c': 4}.

Indice : Il faut utiliser get.

Voici une correction possible.

def add(d1, d2):
    c = {}
    for k, v in d1.items():
        c[k] = v
    for k, v in d2.items():
        c[k] = c.get(k, 0) + v
    return c

Question : Écrivez une fonction qui prend en entrée une chaîne de caractères et retourne un dictionnaire qui compte le nombre d’occurrences de chaque caractère dans la chaîne. Par exemple, si la chaîne d’entrée est “hello”, le dictionnaire de sortie doit être {“h”: 1, “e”: 1, “l”: 2, “o”: 1}.

def count(s):
    pass

count("hello")

Voici une correction possible.

def count(s):
    d = {}
    for l in s:
        d[l] = d.get(l, 0) +1
    return d