Aller au contenu

Dict : Type associatif

Essayons d'écrire un petit programme d'annuaire :

  • On peut renseigner un nouveau contact en donnant son nom et son adresse mail.
  • On peut consulter l'adresse mail d'un contact en donnant son nom.
  • Si on renseigne un contact déjà enregistré, son adresse mail est mise à jour mais un nouveau contact n'est pas créé.

Un problème dans les séquences...

Activité

Expliquez comment faire un tel programme avec des types séquences.

Expliquez également pourquoi cette solution pose problème.

EXPOSEZ VOS REPONSES A VOTRE PROF

L'annuaire comme une liste de paires :

[ ("Félix", "unmail@unboite.com"), ("Julie", "unautremail@uneautreboite.com"), ("Mathilde", "mathilde@uneboite.com"), ("NOM", "MAIL"), ...]

Mais cette solution rends tout très compliqué :

Pour savoir l'adresse de Félix, il faut chercher Félix dans la liste !

Si on veut ajouter un contact, il faut d'abord le chercher pour vérifier qu'il n'est pas déjà dans la liste, sinon il faut le mettre à jour et non l'ajouter.

Heureusement, Python a une solution très pratique pour ça !

Dict : type associatif

Python propose un type, dict, ou dictionnaire, spécialisé dans les associations par paires.

On construit un dictionnaire en spécifiant des paires, entre {} :

un_dico = {
    "Félix" : "f@mail.com",
    "Julie" : "juju38@mail.com",
}

un_dico_vide = {}

Dans une paire, par exemple

"Félix" : "f@mail.com"
On dit que "Félix" est la clé et que "f@mail.com" est la valeur.

On peut très facilement tester si une clé est dans un dictionnaire avec l'opérateur in :

if "Félix" in un_dico:
    # si Félix est dans un_dico...

Si la clé est dans le dictionnaire, on peut retrouver la valeur qui lui est associée avec l'opérateur [] :

mail_felix = un_dico["Félix"]
print(mail_felix) #affiche "f@mail.com"

On peut ajouter une nouvelle paire dans le dictionnaire de la manière suivante :

un_dico["Gérard"] = "tutut@mail.com"

Si la clé est déjà présente dans le dictionnaire, alors la valeur associée est mise à jour :

un_dico = {"Félix" : "f@mail.com"}

print(un_dico["Félix"]) #affiche f@mail.com

un_dico["Felix"] = "felfel@mail.fr"

print(un_dico["Félix"]) #affiche felfel@mail.fr

Donc, les clés dans un dictionnaire sont uniques : il ne peux y avoir qu'une seule clé avec une valeur donnée.

Activité

Implémentez un programme qui :

  • Propose à l'utilisateur de renseigner des contacts tant qu'il ne rentre pas une chaine vide.
  • Propose ensuite à l'utilisateur de retrouver le mail d'un contact à partir de son nom, encore et encore, tant qu'il ne rentre pas une chaîne vide.

TODO : ajouter solution

On peut parcourir les clés d'un dictionnaire avec un for ... in ...:

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

for cle in dico : 
    print(cle) #affiche successivement : a, b, et c

Vous pouvez avoir la liste des clés dans un dictionnaire en utilisant keys ou en le convertissant en liste :

cles = list(dico.keys())
#OU
cles = list(dico)

Vous pouvez avoir la liste des valeurs dans un dictionnaire en utilisant values:

valeur = list(dico.values())

Activité

Modifiez le programme de l'activité précédente pour que si l'utilisateur se trompe demande l'adresse d'un contact qui n'existe pas, la liste des contacts lui soit affichée.

TODO : ajouter solution

Usages et typages

Le dictionnaire a deux utilités.

La première est celle que nous avons vue dans la section précédente : enregistrer un nombre indéterminé de paires, où toutes les clés ont le même type et la même signification, et toutes les valeurs le même type et la même signification. Il remplace donc une liste de paires. Par exemple :

{ NOM : ADRESSE, ...}

{ IDENTIFIANT : NIVEAU, ...}

La seconde est de donner du sens à des valeurs en nombre fixe, comme nous avions vu pour les tuples. Dans ce cas là, les clés sont des chaînes de caractère qui donnent le sens de leur valeurs associées, et les valeurs peuvent avoir n'importe quels types :

personne = {
    "nom" : "Bertoni", #type : str
    "prénom" : "Félix", #type : str
    "age" : 29, #type int
    "niveau" : 200, #type int
    "diplomes" : ["BAC", "DUT", "Ingenieur", "DIU"] #type list[str]
}

Activité

TODO ajouter activité ici : ptet ajouter des info supplémentaire dans le dico ?

Extension : dispatching dynamique

Cette section est une extension, elle est donc optionnelle. Il est quand même intéressant d'y jeter un oeil, même si vous ne la faites pas.

Les dictionnaires sont un outils extrêmement puissant.

Parfois, une série de if...else peut être remplacée par un simple dictionnaire. Et c'est assez cool.

C'est le cas dans calculatrice que vous avez implémentée dans un chapitre antérieur.

Vous trouverez dans le dépliant ci-dessous un exemple du code d'une telle calculatrice :

Code d'une calculatrice
# on définit les opérations
def addition(a, b) :
    return a + b

def soustraction(a, b):
    return a - b

def multiplication(a, b):
    return a * b


# on prends les nombres en entrée
a = int(input("entrez le premier nombre: "))
op = input("entrez l'opération (+ - *) à effectuer: ")

# On vérifie que l'opération existe, sinon on redemande
while not (op == "+" or op == "-" or op == "*"):
    op = input("L'opération doit être + - ou *: ")

b = int(input("entrez le second nombre: "))


#on fait le calcul
res = 0
if op == "+":
    res = addition(a, b)
elif op == "-":
    res = soustraction(a, b)
elif op == "*"
    res = multiplication(a, b)
else :
    print("ERREUR : opération", op, "inconnue !")


#on affiche le résultat
print("résultat:", res)

Activité : vérif

Copiez-collez le code ci dessus dans un fichier et vérifiez qu'il marche. Si besoin, corrigez-le.

Activité : puissance

En Python, l'opérateur puissance \(n^m\) est n ** m.

Ajoutez l'opération puissance dans le code.

def addition(a, b) :
    return a + b

def soustraction(a, b):
    return a - b

def multiplication(a, b):
    return a * b

def puissance(a, b): #fonction
    return a ** b


a = int(input("entrez le premier nombre: "))
op = input("entrez l'opération (+ - * **) à effectuer: ") #on modifie la chaine

while not (op == "+" or op == "-" or op == "*" or op == "**"): #on ajoute une conditioin
    op = input("L'opération doit être + - * ou **: ") #on modifie la chaine

b = int(input("entrez le second nombre: "))


res = 0
if op == "+":
    res = addition(a, b)
elif op == "-":
    res = soustraction(a, b)
elif op == "*":
    res = multiplication(a, b)
elif op =="**": #on ajoue un elif
    res = puissance(a, b)
else :
    print("ERREUR : opération", op, "inconnue !")


print("résultat:", res)

Activité : rétrospection

Comptez combien de lighes dans le code vous avez édité le code pour ajouter la puissance.

def addition(a, b) :
    return a + b

def soustraction(a, b):
    return a - b

def multiplication(a, b):
    return a * b

def puissance(a, b): #1
    return a ** b    #2


a = int(input("entrez le premier nombre: "))
op = input("entrez l'opération (+ - * **) à effectuer: ") #3

while not (op == "+" or op == "-" or op == "*" or op == "**"): #4
    op = input("L'opération doit être + - * ou **: ") #5

b = int(input("entrez le second nombre: "))


res = 0
if op == "+":
    res = addition(a, b)
elif op == "-":
    res = soustraction(a, b)
elif op == "*":
    res = multiplication(a, b)
elif op =="**": #6
    res = puissance(a, b) #7
else :
    print("ERREUR : opération", op, "inconnue !")


print("résultat:", res)

Observez la nature de ces éditions :

  • Modification complexe (difficile à vérifier sans exécuter le code) : condition d'un if / while, calcul, ...
  • Ajout de code
  • Modification triviale (facile à vérifier à la main) : modification d'une chaîne
def addition(a, b) :
    return a + b

def soustraction(a, b):
    return a - b

def multiplication(a, b):
    return a * b

def puissance(a, b): # ajout
    return a ** b    # ajout


a = int(input("entrez le premier nombre: "))
op = input("entrez l'opération (+ - * **) à effectuer: ") # modification triviale

while not (op == "+" or op == "-" or op == "*" or op == "**"): # modification complexe
    op = input("L'opération doit être + - * ou **: ") # modification triviale

b = int(input("entrez le second nombre: "))


res = 0
if op == "+":
    res = addition(a, b)
elif op == "-":
    res = soustraction(a, b)
elif op == "*":
    res = multiplication(a, b)
elif op =="**": # modification complexe (le elif modifie le comportement du if)
    res = puissance(a, b) # ajout
else :
    print("ERREUR : opération", op, "inconnue !")


print("résultat:", res)

Donnez au moins deux risques d'erreur qui peuvent arriver en modifiant le code de cette manière.

Confirmez-les avec votre prof

Un risque d'erreur est de "casser" le code qu'on édite dans les modifications complexes. Par exemple, en modifiant un if.

Un autre risque d'erreur est d'oublier de modifier le code à un endroit. Par exemple, d'oublier d'ajouter ** dans la liste des opérations disponibles :

op = input("L'opération doit être + - ou *: ")  #arf, l'utilisateur ne sait pas qu'il peut utiliser ** !

On peut modifier le code pour faire en sorte qu'ajouter une opération ne demande qu'un groupe d'ajouts (écrire la fonction) et une modification triviale, et donc réduire le risque d'erreurs !

Le saviez-vous ? En python, les fonctions sont des valeurs comme les autres, on peut par exemple les stocker dans des variables !

Activité

Qu'affiche

def fois_deux(a):
    return a * 2

f2 = fois_deux #remarquez, PAS DE PARENTHESES après fois_deux

res = f2(10)
print(res)

Exécutez-le pour vérifier votre réponse !

Le code stocke fois_deux dans la variable f2,

puis appelle la fonction par le biais de la variable f2 sur la valeur 10, ce qui retourne 20 et l'affiche.

On peut se servir de ça pour utiliser un dictionnaire plutôt que des if. En effet, le bloc de code :

res = 0
if op == "+":
    res = addition(a, b)
elif op == "-":
    res = soustraction(a, b)
elif op == "*":
    res = multiplication(a, b)
elif op =="**": # modification complexe (le elif modifie le comportement du if)
    res = puissance(a, b) # ajout
else :
    print("ERREUR : opération", op, "inconnue !")

ASSOCIE un opérateur donné ("+", "-", "*", "**") et une opération (respectivement addition, soustraction, multiplication, puissance).

Activité : le code

Voici un début de modification du code pour utiliser un dict au lieu de ifs.

def addition(a, b) :
    return a + b

def soustraction(a, b):
    return a - b

def multiplication(a, b):
    return a * b

def puissance(a, b): t
    return a ** b   


operations = {
    "+" : addition, 
    "-" : soustraction,
    "*" : multiplication,
    "**" : puissance
}

# La suite n'est pas modifiée, a vous de jouer ! #

a = int(input("entrez le premier nombre: "))
op = input("entrez l'opération (+ - * **) à effectuer: ") 

while not (op == "+" or op == "-" or op == "*" or op == "**"): 
    op = input("L'opération doit être + - * ou **: ") 

b = int(input("entrez le second nombre: "))


res = 0
if op == "+":
    res = addition(a, b)
elif op == "-":
    res = soustraction(a, b)
elif op == "*":
    res = multiplication(a, b)
elif op =="**": 
    res = puissance(a, b) 
else :
    print("ERREUR : opération", op, "inconnue !")


print("résultat:", res)

Finissez le travail !

def addition(a, b) :
    return a + b

def soustraction(a, b):
    return a - b

def multiplication(a, b):
    return a * b

def puissance(a, b): t
    return a ** b   


operations = {
    "+" : addition, 
    "-" : soustraction,
    "*" : multiplication,
    "**" : puissance
}


a = int(input("entrez le premier nombre: "))
op = input("entrez l'opération " + str(list(operations))) + " à effectuer: ") 

while op not in operations: 
    op = input("L'opération doit être " + str(list[operations]) + " : ") 

b = int(input("entrez le second nombre: "))


if op not in operations: #normalement, impossible
    print("ERREUR : operation", op, "inconnue")
else :
    res = operations[op](a, b)
    print("résultat")

Activité : vérification

Ajoutez la division, et confirmez que vous avez bien eu à n'écrire qu'une fonction et à faire une modification triviale.