Le type abstrait dictionnaire (appelé aussi tableau associatif) et une structure de données dont le nom est très évocateur. Comme dans un dictionnaire linguistique,
une clef (le mot) est associé à une valeur (la définition).
Par contre, contrairement à un dictionnaire linguistique, la structure de données n'est pas du tout ordonnée, nous verrons plus tard que le mode de recherche dans les dictionnaires
est néanmoins très optimisé. Voici quelques exemples de données adaptées à cette structure :
Un dictionnaire peut aussi regrouper plusieurs informations de natures différentes :
Ici prénom, nom, classe, spécialité 1, spécialité 2, option fac et date de naissance sont les clefs.
Et Léo, Gruoleb, 111, NSI, Latin, 03/12/2004 sont les valeurs.
Comme vous le savez, le type dictionnaire existe déjà en python, vous l'avez sûrement déjà expérimenté l’année dernière . Cependant, avant de "soulever le capot" du langage de programmation
et de tenter de mettre en place nous même ce type abstrait de données nous allons un peu utiliser les dictionnaire en python, afin de nous remémorer leur fonctionnement.
Voici donc 2 exercices simples qui vous permettrons de vous remettre en tête le fonctionnement des dictionnaires en python.
Si vous manquez de bases...Tout est là.
définir le dictionnaire personne contenant les informations suivantes relatives à une personne :
Le dictionnaire scrabble ci-dessous contient la valeur de chacune des lettres de l'alphabet au Scrabble :
scrabble = {"A":1,"B":3,"C":3,"D":2,"E":1,"F":4,"G":2,"H":4,"I":1,"J":8,"K":10,"L":1,"M":2,"N":1,"O":1,"P":3,"Q":8,"R":1,"S":1,"T":1,"U":1,"V":4,"W":10,"X":10,"Y":10,"Z":10}
score
qui calcule, en utilisant le dictionnaire scrabble, le score obtenu en plaçant un mot passé en argument à la fonction ( sans tenir compte des cases bonus : lettre compte-double,
mot compte-triple, etc...) doctest
) :
score('NSI') = 3
score('BONJOUR') = 16
score('KIWI') = 22
score('ANTICONSTITUTIONNELLEMENT') = 28
Revenons maintenant au TAD "dictionnaire". Avant de l'implémenter étudions en détail ce TAD :
Pour "faire fonctionner" un dictionnaire, il faut disposer des opérations suivantes :
Les dictionnaires peuvent être implémentées de différentes façons, comme vous avez déjà une bonne expérience de ce genre d'exercice nous n'aborderons qu'un seul codage :
Le codage grâce à un tableau indexé (une liste python).
Cette solution de codage va consister à stocker dans un tableau des tableaux de deux éléments, le premier étant la clef et le seconde la valeur.
Vous devez coder les fonctions primitives suivantes :
creer_dico_vide():
qui créé un dictionnaire vide ajouter(dico,clef,valeur):
qui ajoute dans le dictionnaire dico la clef clef
et lui associe la valeur valeur
la fonction renvoie le dictionnaire modifié modif(dico,clef,valeur):
qui modifie dans le dictionnaire dico la valeur associée à la clefclef
. la fonction renvoie le dictionnaire modifié supprimer(dico,clef):
supprime dans le dictionnaire dico
la clef clef
et la valeur associée. rechercher(dico,clef):
recherche dans le dictionnaire dico
la clef clef
et affiche la valeur associée. Si la clef n'est pas présente dans le dictionnaire, un message d'erreur est affichéComme vous avez pu le voir en codant la fonction rechercher
, il est nécessaire de parcourir le dictionnaire pour trouver une clef...Pourtant une des particularités des
dictionnaires (notamment en python), c'est de réaliser des recherches en "temps constant".
Cela signifie que le temps de recherche dans un dictionnaire n'augmente pas avec sa taille :
Il est aussi rapide de rechercher une clef dans un dictionnaire 100 éléments que dans un dictionnaire de 1 000 000 000 éléments !
Une seule question : comment ça marche ?
Et bien contrairement à ce que nous avons fait dans la partie précédente, les clef ne sont pas rangées au hasard en mémoire, mais leur place est déterminée par la valeur de la clef elle même.
Il existe ainsi une fonction de hashage qui associe à chaque clef un entier qui déterminera la place des données stockées en mémoire. le processus se fait donc en deux temps :
hashage(clef)
renvoie un entier qui détermine la place de la clef et de la valeur en mémoire. Nous ne détaillerons pas les difficultés que l'on peut rencontrer pour construire une fonction de hashage, cependant :
Il est très difficile de construire une fonction de hashage fiable, car la diversité des clefs est potentiellement infinie et la fonction renvoie vers un nombre fini d'emplacements mémoire.
Il faut à tout prix éviter les collisions, c'est à dire que deux clefs différentes pointent vers le même emplacement mémoire.
Nous ne pouvons pas mettre en œuvre une fonction de hashage complexe (et d'ailleurs ce n'est pas au programme de terminale), mais pour bien comprendre le fonctionnement de la recherche en O(1) dans un dictionnaire vous allez améliorer votre travail précédent pour simuler la recherche en temps constant dans un dictionnaire.
Nous allons nous intéresser à un dictionnaire dont les clefs ne peuvent être que des caractères de la table AASCII. la taille du dictionnaire sera adonc limitée à 128 éléments maximum.
La fonction de hashage que nous allons utiliser est la fonction ord()
. cette fonction renvoie le code ASCII correspondant à un caractère. Ainsi :
>>> ord('a')
97
Vous devez donc modifier les fonctions primitives suivantes :
creer_dico_vide():
qui créé un dictionnaire vide contenant 128 éléments ''
ajouter(dico,clef,valeur):
qui ajoute dans le dictionnaire dico la clef clef
et lui associe la valeur valeur
la fonction renvoie le dictionnaire modifié. Le couple [clef,valeur]
est insérer à l'index ord(clef)
dans le tableau rechercher(dico,clef):
recherche dans le dictionnaire dico
la clef clef
et affiche la valeur associée. Si la clef n'est pas présente dans le dictionnaire, un message d'erreur est affiché. La recherche ne nécessite pas ici de boucle mais l'utilisation de la fonction ord()
pour retrouver la place de la clef dans le tableau.Nous allons maintenant faire quelques mesures de temps de recherche dans un tableau et dans un dictionnaire en utilisant les type construits existant dans python.
Voici la démarche que nous allons suivre :
in
(rechercher une valeur non présente nous garanti que l'on parcourra
l'ensemble des structures de données)La mesure de temps se fera grâce au module timeit
qui permet de systématiser ce genre de tests. Voici le code de départ :
from random import *
from timeit import timeit
liste =[randint(0,1000) for i in range (1000000)]
dictionnaire={randint(0,1000): randint(0,1000) for i in range(1000000)}
print('liste : ',timeit(stmt='1001 in liste', globals=globals(), number=1000))
print('dictionnaire ',timeit(stmt='1001 in dictionnaire', globals=globals(), number=1000))
Les paramètres de timit sont les suivants :
stmt
correspond à l’instruction qui sera exécutée. globals
permet d'utiliser les structures de données créées en dehors de la chaine stmt
précédentenumber
permet d'exécuter l'instruction contenue dans stmt
autant de fois que nécessaire (afin d'être sur de ne pas tomber sur un état particulier dans les processus au moment de la mesure du temps)Vous devez maintenant :