Dictionnaires, Ensembles & Applications IA

Cours 4 — L'art de tout ranger dans des tables
🔑 dict · set · Bag of Words · Cache · One-Hot

§1 Rappel — le problùme des listes

Dans les cours précédents, on a stocké les données dans des listes : une liste de notes, une liste de tailles, une matrice d'images. Les listes, c'est génial pour les séquences ordonnées. Mais pour chercher un élément par sa valeur, c'est lent : il faut parcourir toute la liste.

# Chercher "Bob" dans une liste de noms — O(n)
annuaire_liste = ["Alice", "Bob", "Charlie", "Dana"]
# Pour trouver le numéro de Bob, on parcourt... pas terrible

# Avec un dictionnaire — O(1) en moyenne
annuaire_dict = {"Alice": "0601020304",
                 "Bob":   "0605060708",
                 "Charlie": "0609101112"}
print(annuaire_dict["Bob"])  # Immédiat
0605060708
Chercher dans une liste, c'est comme fouiller une pile de copies pour trouver celle de Dupont. Chercher dans un dictionnaire, c'est comme ouvrir le bottin Ă  la lettre D. Le dictionnaire Python, c'est le bottin magique qui sait toujours oĂč se trouve Dupont. Sauf qu'il peut aussi ranger des elephants, des recettes de cookies, ou les paramĂštres d'un rĂ©seau de neurones.

§2 Les dictionnaires : des paires clĂ© → valeur

Un dictionnaire associe des clés (keys) à des valeurs (values). Comme un vrai dictionnaire : on cherche un mot (clé), on trouve sa définition (valeur).

2.1 Création et accÚs

# Création avec accolades (comme un set, mais avec des ':' )
vide = {}
notes = {"Alice": 14, "Bob": 8, "Charlie": 16}

# AccÚs par clé
print(notes["Alice"])   # 14

# Ajouter / modifier
notes["Dana"] = 12      # Ajoute une nouvelle entrée
notes["Bob"] = 10       # Modifie la note de Bob

print(notes)
{'Alice': 14, 'Bob': 10, 'Charlie': 16, 'Dana': 12}
Attention : notes["Bob"] si "Bob" n'existe pas → KeyError. Utilisez notes.get("Bob", 0) pour une valeur par dĂ©faut, ou testez avec "Bob" in notes.
MéthodeCe qu'elle fait
d.get(k, def)Retourne d[k] ou def si la clé manque
d.keys()Vue sur toutes les clés
d.values()Vue sur toutes les valeurs
d.items()Vue sur les paires (clé, valeur)
d.pop(k)EnlÚve la clé k et retourne sa valeur
d.update(d2)Fusionne le dictionnaire d2 dans d

2.2 Parcourir un dictionnaire

# Parcours des clés
for nom in notes:
    print(nom, "a", notes[nom])

# Parcours des paires (clé, valeur)
for nom, note in notes.items():
    print(nom, ":", note)

# Avec une compréhension (comme pour les listes !)
adm = {nom: note for nom, note in notes.items() if note >= 10}
print(adm)  # {'Alice': 14, 'Charlie': 16, 'Dana': 12}
Alice a 14
Bob a 10
Charlie a 16
Dana a 12
{'Alice': 14, 'Charlie': 16, 'Dana': 12}

2.3 Table de hachage — la magie derriùre le rideau

Les dictionnaires Python sont implĂ©mentĂ©s comme des tables de hachage (hash tables). Le principe : on passe la clĂ© dans une fonction de hachage qui produit un nombre (le hash), et ce nombre sert d'index pour stocker la valeur dans un tableau interne. RĂ©sultat : un accĂšs en temps constant O(1) en moyenne — quelle que soit la taille du dictionnaire.

L'idée vient de Hans Peter Luhn (IBM, 1953), mais c'est Python qui l'a rendue populaire avec sa syntaxe élégante. Sans les tables de hachage, pas de cache web, pas de bases de données rapides, pas de in instantané.
# Les clĂ©s doivent ĂȘtre « hashables » (immuables)
# → str, int, float, tuple OK
# → list, dict, set  PAS OK (mutables → hash non stable)

# Le hash d'une chaĂźne :
print(hash("Bob"))         # par exemple : 619161574
print(hash("Alice"))       # différent (normal)
print(hash((1, 2)))       # un tuple marche
# print(hash([1, 2]))  # TypeError ! Les listes ne sont pas hashables
619161574-1725368475
371308163
Le hash d'une chaĂźne, c'est un peu son empreinte digitale. Deux chaĂźnes diffĂ©rentes peuvent avoir le mĂȘme hash (collision), mais Python gĂšre ça avec Ă©lĂ©gance. Les collisions, c'est comme les homonymes dans l'annuaire : on sait les dĂ©partager. Sinon, ça serait le chaos.

§3 Applications IA avec les dictionnaires

3.1 Comptage de mots — la frĂ©quence, en mieux

On reprend l'idée du cours 2 (compter des notes), mais avec des mots. Un dictionnaire est parfait : clé = mot, valeur = nombre d'occurrences.

def compte_mots(texte):
    """Compte les occurrences de chaque mot dans un texte.
    Retourne un dict {mot: compte}.
    """
    # On met tout en minuscules et on sépare les mots
    mots = texte.lower().split()
    frequences = {}
    
    for mot in mots:
        # Technique 1 : si le mot n'existe pas, l'initialiser Ă  1
        if mot in frequences:
            frequences[mot] = frequences[mot] + 1
        else:
            frequences[mot] = 1
    return frequences

# Technique 2 — plus Ă©lĂ©gante avec .get()
def compte_mots_v2(texte):
    mots = texte.lower().split()
    frequences = {}
    for mot in mots:
        frequences[mot] = frequences.get(mot, 0) + 1
    return frequences

# Technique 3 — avec collections.Counter (bonus)
from collections import Counter

texte = "python java python javascript python java python"
freq = compte_mots_v2(texte)
print(freq)
# {'python': 4, 'java': 2, 'javascript': 1}
{'python': 4, 'java': 2, 'javascript': 1}
Le comptage de mots, c'est la base du Traitement Automatique des Langues (NLP). Avant les transformers et ChatGPT, tous les modÚles de langage commençaient par compter les mots. Le modÚle Bag of Words (sac de mots) ignore l'ordre des mots et ne garde que leurs fréquences. C'est naïf, mais ça marche étonnamment bien pour la classification de textes (spam, sentiment, langue). Et ça tient en 3 lignes de Python.

3.2 One-Hot Encoding — mettre les mots en nombres

Pour qu'un algorithme d'IA comprenne des mots, il faut les numériser. Le one-hot encoding crée un vecteur de la taille du vocabulaire, avec un 1 à la position du mot et des 0 partout ailleurs.

def one_hot(vocabulaire):
    """Crée un dictionnaire {mot: vecteur_one_hot}.
    vocabulaire est une liste de mots uniques.
    """
    taille = len(vocabulaire)
    vecteurs = {}
    for i, mot in enumerate(vocabulaire):
        vecteur = [0] * taille
        vecteur[i] = 1
        vecteurs[mot] = vecteur
    return vecteurs

vocab = ["python", "java", "javascript"]
encodage = one_hot(vocab)

print("python", "→", encodage["python"])
print("java", "→", encodage["java"])
print("javascript", "→", encodage["javascript"])
python → [1, 0, 0]
java → [0, 1, 0]
javascript → [0, 0, 1]
Le one-hot encoding, c'est comme les badges Ă  un concert : un mot = un badge allumĂ©. « python » allume le premier badge, « java » le deuxiĂšme. ProblĂšme : pour un vocabulaire de 50 000 mots, votre vecteur fait 50 000 dimensions avec un seul 1. C'est du gĂąchis (on dit sparse). Les embeddings (Word2Vec, GloVe) sont la version compacte — mais ça, c'est pour plus tard.

3.3 MĂ©moĂŻsation — accĂ©lĂ©rer la rĂ©cursivitĂ©

Souvenez-vous du Fibonacci exponentiel du cours 1. On peut le rendre linéaire en mémorisant les résultats déjà calculés dans un dictionnaire (cache). C'est la mémoïsation.

def fib_memo(n, cache={0: 0, 1: 1}):
    """Fibonacci avec mĂ©moĂŻsation — O(n) au lieu de O(2ⁿ).
    
    Le paramÚtre cache={...} est évalué une fois à la définition
    et conservé entre les appels. C'est un « accumulateur magique ».
    """
    if n in cache:
        return cache[n]
    cache[n] = fib_memo(n - 1, cache) + fib_memo(n - 2, cache)
    return cache[n]

print(fib_memo(10))    # 55
print(fib_memo(50))    # 12586269025 — instantanĂ© !
print(fib_memo(100))   # 354224848179261915075
55
12586269025
354224848179261915075
La mémoïsation transforme un algorithme exponentiel en un algorithme linéaire. Tout ça grùce à un dictionnaire. Les dictionnaires sont donc officiellement des accélérateurs de calcul. C'est comme mettre un turbo sur une 2CV. En IA, on mémoïse tout : les gradients, les activations, les chemins dans les arbres. Le cache est roi.
Principe fondamental : un problĂšme difficile (calculer Fibonacci(100)) peut ĂȘtre rĂ©solu en dĂ©composant en sous-problĂšmes plus petits (Fibonacci(99) + Fibonacci(98)), Ă  condition de ne pas recalculer les mĂȘmes sous-problĂšmes. C'est la base de la programmation dynamique, inventĂ©e par Richard Bellman (annĂ©es 1950). Bellman disait : « la programmation dynamique, c'est de la rĂ©cursivitĂ© avec de la mĂ©moire ». En Python : rĂ©cursivitĂ© + dictionnaire = puissance.

3.4 Index inversĂ© — le moteur de recherche du pauvre

Un index inversĂ© associe chaque mot d'un corpus Ă  la liste des documents qui le contiennent. C'est le cƓur de tous les moteurs de recherche (Google, Elasticsearch, etc.).

def index_inverse(documents):
    """Construit un index inversé : {mot: [id_doc1, id_doc2, ...]}.
    documents est une liste de chaĂźnes.
    """
    index = {}
    for doc_id, texte in enumerate(documents):
        mots = set(texte.lower().split())  # set = mots uniques
        for mot in mots:
            if mot not in index:
                index[mot] = []
            index[mot].append(doc_id)
    return index

docs = [
    "Python est génial pour l'IA",
    "Java est génial pour le web",
    "Python et l'IA sont géniaux"
]

idx = index_inverse(docs)
print(idx["python"])  # [0, 2] — prĂ©sent dans doc 0 et 2
print(idx["gĂ©nial"])  # [0, 1] — prĂ©sent dans doc 0 et 1

# Recherche : quels documents contiennent "python" ET "IA" ?
resultat = set(idx.get("python", [])) & set(idx.get("ia", []))
print(resultat)  # {0, 2}
[0, 2]
[0, 1]
{0, 2}
FĂ©licitations, vous venez d'implĂ©menter un mini-Google. C'est moins bien indexĂ©, ça n'a pas de PageRank, et ça ne vous rapportera pas 100 milliards de dollars. Mais le principe est exactement le mĂȘme. (Si vous voulez les 100 milliards, il faut ajouter 99 999 999 900 $ de fonctionnalitĂ©s. Bon courage.)

§4 Les ensembles (set) : des listes sans doublons

Un set (ensemble) est une collection non ordonnĂ©e d'Ă©lĂ©ments uniques. C'est comme un dictionnaire sans valeurs — juste des clĂ©s.

4.1 Création et opérations de base

# Création
vide = set()         # {} crée un dict vide, pas un set !
couleurs = {"rouge", "vert", "bleu", "rouge"}  # les doublons sont ignorés
print(couleurs)  # {'rouge', 'vert', 'bleu'} — l'ordre n'est pas garanti

# Ajout / suppression
couleurs.add("jaune")
couleurs.discard("vert")  # ignore si absent
print(couleurs)

# Test d'appartenance — O(1) (comme les clĂ©s de dict)
print("rouge" in couleurs)  # True
print("violet" in couleurs)  # False
{'bleu', 'rouge', 'vert'}
{'rouge', 'bleu', 'jaune'}
True
False

4.2 Opérations ensemblistes

A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7}

print(A | B)   # Union           : {1, 2, 3, 4, 5, 6, 7}
print(A & B)   # Intersection    : {4, 5}
print(A - B)   # Différence      : {1, 2, 3}
print(A ^ B)   # Diff. symétrique: {1, 2, 3, 6, 7}

# Sous-ensemble / sur-ensemble
print({1, 2} <= A)  # True — {1,2} ⊆ A
print(A >= {1, 2})  # True — A ⊇ {1,2}
{1, 2, 3, 4, 5, 6, 7}
{4, 5}
{1, 2, 3}
{1, 2, 3, 6, 7}
True
True
Les opĂ©rations sur les ensembles ont Ă©tĂ© formalisĂ©es par Georg Cantor (1845–1918) — le crĂ©ateur de la thĂ©orie des ensembles. Il a montrĂ© qu'il existe diffĂ©rents « infinis » (dĂ©nombrable, continu...). Ses contemporains l'ont traitĂ© de fou. Aujourd'hui, sa thĂ©orie est la base de toutes les mathĂ©matiques modernes. Et aussi des sets Python. Cantor aurait Ă©tĂ© content de savoir que {1,2,3} | {3,4,5} existe et marche du premier coup.

4.3 Applications pratiques

# Supprimer les doublons d'une liste
notes_avec_doublons = [12, 15, 8, 12, 15, 10]
notes_uniques = list(set(notes_avec_doublons))
print(notes_uniques)  # [8, 10, 12, 15] (ordre quelconque)

# Mots communs entre deux textes
texte1 = "Python IA apprentissage profond"
texte2 = "Python web java IA"
mots1 = set(texte1.lower().split())
mots2 = set(texte2.lower().split())
print("Communs :", mots1 & mots2)   # {'python', 'ia'}
print("Union :", mots1 | mots2)     # {'python', 'ia', 'apprentissage', 'profond', 'web', 'java'}

# Vérifier qu'une liste a des doublons
def a_doublons(liste):
    return len(liste) != len(set(liste))

print(a_doublons([1, 2, 3]))     # False
print(a_doublons([1, 2, 1]))     # True
[8, 10, 12, 15]
Communs : {'ia', 'python'}
Union : {'python', 'web', 'profond', 'java', 'apprentissage', 'ia'}
False
True

Variation : Jaccard — similaritĂ© entre ensembles

Le coefficient de Jaccard mesure la similarité de deux ensembles :

def jaccard(A, B):
    if len(A | B) == 0:
        return 0
    return len(A & B) / len(A | B)

print(jaccard({1,2,3}, {2,3,4}))  # 2/4 = 0.5
# TrÚs utilisé en NLP pour comparer des documents !
0.5

§5 Application complÚte : Bag of Words (sac de mots)

On assemble tout ce qu'on a vu pour construire un classifieur de textes minimal : on transforme chaque document en un vecteur de fréquences (grùce à un dictionnaire de vocabulaire), et on compare les vecteurs.

def sac_de_mots(textes):
    """Crée la matrice TF (term frequencies) pour une liste de textes.
    
    Retourne :
        vocabulaire — dict {mot: index}
        matrice    — liste de listes, chaque ligne est un vecteur de frĂ©quences
    """
    # Étape 1 : construire le vocabulaire (ensemble de tous les mots)
    vocab = set()
    for texte in textes:
        mots = texte.lower().split()
        vocab.update(mots)   # ajoute tous les mots au set
    
    # Transformer le set en dict {mot: index}
    vocab_liste = sorted(vocab)  # ordre stable
    vocab_dict = {mot: i for i, mot in enumerate(vocab_liste)}
    
    # Étape 2 : pour chaque texte, crĂ©er un vecteur de frĂ©quences
    matrice = []
    for texte in textes:
        vecteur = [0] * len(vocab)
        mots = texte.lower().split()
        for mot in mots:
            index = vocab_dict[mot]
            vecteur[index] = vecteur[index] + 1
        matrice.append(vecteur)
    
    return vocab_dict, matrice

textes = [
    "l'IA va dominer le monde",
    "Python domine le monde de l'IA",
    "le monde est vaste"
]

vocab, matrice = sac_de_mots(textes)
print("Vocabulaire :", vocab)
# Chaque ligne = un texte, chaque colonne = un mot
for i, vect in enumerate(matrice):
    print("Texte", i, ":", vect)
Vocabulaire : {'de': 3, "l'ia": 4, 'domine': 2, 'est': 5, 'le': 6, 'monde': 7, 'python': 0, 'va': 1, 'vaste': 8}
Texte 0 : [0, 1, 1, 1, 1, 0, 1, 1, 0]
Texte 1 : [1, 0, 1, 1, 1, 0, 1, 1, 0]
Texte 2 : [0, 0, 0, 0, 0, 1, 1, 1, 1]
Ce que vous voyez, c'est la représentation numérique de trois phrases. Chaque vecteur est l'« ADN » du texte. « l'IA va dominer le monde » et « Python domine le monde de l'IA » se ressemblent beaucoup (elles parlent de domination mondiale). La troisiÚme (« le monde est vaste ») est différente (elle est philosophique). Les algorithmes de clustering (TD à venir) repÚrent ça tout seuls.

§6 Pour aller plus loin

6.1 defaultdict — le dictionnaire sans erreur

from collections import defaultdict

# Un dict qui initialise automatiquement les clés manquantes
freq = defaultdict(int)   # int() → 0
groupes = defaultdict(list)  # list() → []

for mot in ["a", "b", "a"]:
    freq[mot] = freq[mot] + 1  # pas de KeyError !

print(freq)  # {'a': 2, 'b': 1}

6.2 Dictionnaires ordonnés (Python 3.7+)

Depuis Python 3.7, les dictionnaires conservent l'ordre d'insertion. C'est pratique, mais ne comptez pas dessus pour trier — utilisez sorted().

6.3 frozenset — le set immuable (donc hashable)

# Utile quand on veut un set comme clé de dictionnaire
equipes = {frozenset(["Alice", "Bob"]): "Équipe A"}
# frozenset est immuable, donc hashable, donc utilisable comme clé

6.4 Limites : la mémoire des dictionnaires

Les dictionnaires sont rapides mais gourmands en mémoire (la table de hachage occupe de l'espace). Pour des millions d'entrées, il faut des structures spécialisées. Mais pour 99% des cas, un dict fait l'affaire.


§7 Le mot de la fin

Avec les dictionnaires et les ensembles, vous avez dĂ©sormais toutes les structures de donnĂ©es fondamentales de Python : listes, tuples, dictionnaires, sets. Vous ĂȘtes Ă©quipĂ©s pour la quasi-totalitĂ© des problĂšmes d'IA.

Pour terminer, une citation de Larry Wall (créateur de Perl) : « Les trois grandes vertus d'un programmeur sont : la paresse, l'impatience, et l'orgueil. »

Les dictionnaires incarnent la paresse : pourquoi parcourir une liste quand on peut avoir la valeur en O(1) ?
Les compréhensions incarnent l'impatience : pourquoi écrire 5 lignes quand une suffit ?
Et le code élégant incarne l'orgueil : vous voulez que les autres développeurs admirent votre code. (C'est pour ça qu'on met des commentaires. Par orgueil.)
Bilan du cours 4 : De quoi briller en soirée (sujet : les dictionnaires Python). Bon, d'accord, non. Mais au moins en entrevue technique.
© 2026 — laurent.thiry@uha.fr