====== Dictionnaires ====== ===== Problématique ===== Un tableau permet d'organiser des données en associant un item à un indice. ''%%tableau[indice]%%'' => ''%%item%%'' de rang ''%%i%%'' ==== Exemple : Jours de la semaine ==== jours = ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"] Avec ce tableau on obtient facilement le jour d'indice 5 : >>> jours[5] "ven" Mais comment faire des associations de type différent ? ==== Exemple : Gentilé ==== Par exemple, on souhaite associer le nom d'une commune avec son gentilé (nom des habitants) : gentiles = [ ("Paris", "Parisien"), ("Marseille", "Marseillais"), ("Saint-Denis", "Dionysien"), ("Château-Thierry", "Castelthéodoricien") ] Comment obtenir le gentilé pour ''%%"Saint-Denis"%%'' ? > C'est possible mais cela suppose de parcourir le tableau à la recherche de ''%%"Saint-Denis"%%''. On irait plus vite si on pouvait écrire quelque chose comme ''%%gentiles["Saint-Denis"]%%'' def ville_to_gentile(ville, gentiles): """ ville: ville recherchée gentiles: liste des paires (ville, gentilé) renvoie le gentilé associé à une ville, None si pas trouvé """ for v, g in gentiles: if v == ville: return g return None # pas trouvé Cette méthode fonctionne mais trouver le gentilé associé à une ville peut prendre du temps puisqu'il faut parcourir le tableau jusqu'à trouver la ville. ==== Dictionnaire ==== Un dictionnaire consiste justement à créer de telles associations. On pourra écrire : gentiles = { "Paris":"Parisien", "Marseille":"Marseillais", "Saint-Denis":"Dionysien", "Château-Thierry":"Castelthéodoricien" } Et pour obtenir le gentilé de Saint-Denis il suffit d'écrire >>> gentiles["Saint-Denis"] "Dionysien" ==== Clé et valeur ==== **Attention :** Dans le dictionnaire, les deux éléments d'une paire n'ont pas le même rôle. La partie à gauche de ''%%:%%'' est la **clé**, à droite c'est la **valeur** >>> gentiles.keys() ['Paris', 'Marseille', 'Saint-Denis', 'Château-Thierry'] >>> gentiles.values() ['Parisien', 'Marseillais', 'Dionysien', 'Castelthéodoricien'] Et donc cette commande renvoie une erreur : >>> gentiles["Dionysien"] KeyError: "Dionysien" car ''%%"Dionysien"%%'' est une valeur, pas une clé ! On retrouve la différence entre **indice** et **valeur** dans un [[.:tableaux|tableau]]. L'indice d'un tableau est toujours un nombre entier naturel. Une clé de dictionnaire est comme l'indice mais la clé peut être autre chose qu'un entier. En Python, la clé peut être de types ''%%int%%'', ''%%float%%'', ''%%str%%'', ''%%tuple%%''. **Un peu de technique :** Python transforme la clé en utilisant la représentation de la donnée en mémoire et en utilisant une fonction mathématique, un //hash//. Ainsi, tout se passe comme si nous avions un indice entier. Il est donc important que la clé ait une représentation mémoire fixe. Un ''%%list%%'' ne convient pas car on peut modifier son contenu. ===== Manipulations élémentaires ===== On poursuit les exemples avec gentiles = { "Paris":"Parisien", "Marseille":"Marseillais", "Saint-Denis":"Dionysien", "Château-Thierry":"Castelthéodoricien" } ==== Nombre de paires clé-valeur ==== >>> len(gentiles) 4 ==== Appartenance ==== ''%%in%%'' permet de savoir si une **clé** est présente. >>> "Paris" in gentiles True >>> "Lille" in gentiles False >>> "Dionysien" in gentiles False Attention, ''%%"Dionysien"%%'' est bien dans le dictionnaire mais en tant que valeur, pas en tant que clé. ==== Lecture ==== On retrouve la même syntaxe que pour les [[p-uplets_et_tableaux|p-uplets]] et les [[.:tableaux|tableaux]] >>> gentiles["Château-Thierry"] 'Castelthéodoricien' >>> gentiles["Lille"] KeyError Pour éviter les erreurs, on peut aussi utiliser : >>> gentiles.get("Château-Thierry") 'Castelthéodoricien' >>> gentiles.get("Lille") le dernier ne renvoie rien (renvoie ''%%None%%'') car ''%%"Lille"%%'' n'est pas une clé du dictionnaire. Ne lève pas d'erreur. ==== Ajout et modification ==== >>> gentiles["Lille"] = "Lillois" >>> gentiles["Paris"] = "Parisienne" Dans le premier cas, c'est une création de paire clé-valeur, dans le deuxième cas, c'est une modification. ==== Suppression ==== >>> del gentiles["Château-Thierry"] ==== Énumération - boucle for ==== Parcourir les éléments d'un dictionnaires. Comme pour un tableau, on peut désirer parcourir les clés, les valeurs ou les deux à la fois. === Parcourir par clé === for key in gentiles: # boucle se répète pour chaque clé # key prendra les valeurs "Paris", "Saint-Denis"... **Attention !** Dans un dictionnaire, l'ordre n'est pas garanti ! Ce n'est pas parce que on a créé le dictionnaire ainsi : gentiles = { "Paris":"Parisien", "Marseille":"Marseillais", "Saint-Denis":"Dionysien", "Château-Thierry":"Castelthéodoricien" } que les clés seront parcourues dans l'ordre ''%%"Paris", "Marseille", "Saint-Denis,...%%''. **L'ordre est imprévisible**. === Parcourir par valeurs === Peu utile. for item in gentiles.values(): # répété pour chaque paire, ordre imprévisible # item prend les valeurs "Parisien", "Marseillais"... === Parcourir par paires === for key, item in gentiles.items(): # parcours simultané des clé et valeurs # key vaut la clé, item la valeur associée # toujours dans un ordre imprévisible... C'est l'équivalent de la situation rencontrée avec les [[.:tableaux#Par indice et item|tableaux]] : for indice, item in enumerate(tableau): # parcours simultané des indices et valeurs ===== Tuple nommé ===== Le programme de NSI fait référence à des **tuples nommés**. Ce type de donnée n'existe pas nativement en Python (il faut importer une bibliothèque) L'idée du programme est simplement d'utiliser, dans certains cas, des dictionnaires comme des tuples. **Cependant attention :** un dictionnaire est mutable mais pas un tuple (on ne peut pas modifier un tuple une fois créé), c'est d'ailleurs ce qui fait la différence entre un tuple et un tableau. ===== Efficacité d'un dictionnaire ===== Comme on l'a dit, on pourrait créer un tableau de paires de valeurs ce qui permettrait, en parcourant le tableau, de retrouver tout ce que l'on veut : gentiles = [ ("Paris", "Parisien"), ("Marseille", "Marseillais"), ("Saint-Denis", "Dionysien"), ("Château-Thierry", "Castelthéodoricien") ] Mais (déjà dit) pour trouver le gentilé de ''%%"Saint-Denis"%%'' (par exemple) il faut parcourir le tableau. Cela peut-être long si le tableau est grand. Dans le cas d'un tableau, par exemple : jours = ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"] L'accès à un élément par son indice est direct : >>> jours[5] "ven" C'est à dire qu'il n'est pas nécessaire de parcourir ''%%"dim"%%'', ''%%"lun"%%'', etc. pour atteindre ''%%"ven"%%'', le programme saute directement à la bonne position. Le dictionnaire permet de faire la même chose. **La suite est un peut technique et est réservée au élèves plus avancés.** De façon cachée pour l'utilisateur, quand on crée un dictionnaire, Python crée un tableau avec une taille fixée, par exemple 3. Mais les clés de notre dictionnaire ne sont pas des nombres et ne sont donc pas des indices acceptables. Alors Python fait un calcul avec la clé : >>> hash("Paris") 7211877366768910205 Il faut comprendre que ''%%"Paris"%%'', en mémoire est représenté comme un code binaire et que ce code binaire peut-être interprété comme un nombre et que l'on peut donc produire un nombre avec. Ce nombre doit servir d'indice pour notre tableau. Mais notre tableau n'a que 3 cases alors : >>> hash("Paris") % 3 2 On place donc la paire ''%%("Paris", "Parisien")%%'' au rang 2 du tableau. Faisant le même travail avec les autres, Python créé (toujours de façon cachée) : [ [], [("Saint-Denis", "Dionysien")], [("Paris", "Parisien"), ("Marseille", "Marseillais")] ] Ensuite quand je demande ''%%gentiles["Paris"]%%'', le calcul ''%%hash("Paris") % 3%%'' nous dit que l'on doit regarder à la case 2 et chercher dans les items qui s'y trouvent. Cela donne de bons résultats si chaque case est peu remplie. Python peut modifier la taille du tableau si le nombre d'éléments augmente, tout cela sans que l'utilisateur ait quoi que ce soit à faire. Ce mécanisme est appelé une **table de hachage**.