English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

nu

invité
1 / ?
retour aux leçons

Ce qui rend une liste utile

Listes : votre première structure de données

Une liste (appelée tableau dans certains langages) stocke les éléments dans un ordre spécifique. Vous pouvez :

- Accéder à n'importe quel élément par sa position : names[0] récupère le premier élément. O(1) — instantané.

- Ajouter des éléments à la fin : names.append("Zoe"). O(1) — instantané.

- Parcourir chaque élément dans l'ordre : for name in names. O(N) — visite chaque élément une fois.

Les listes préservent l'ordre dans lequel vous mettez les choses. Le premier élément reste premier. Le dernier reste dernier.

# A list of pets
pets = ["cat", "dog", "cat", "fish", "dog"]

print(pets[0])   # "cat"  — O(1) instant
print(pets[3])   # "fish" — O(1) instant
print(len(pets)) # 5 — duplicates kept

Remarque : « cat » apparaît deux fois. « dog » apparaît deux fois. Les listes permettent les doublons. Elles stockent exactement ce que vous leur donnez, dans l'ordre où vous l'avez donné.

Mais les listes ont une faiblesse. Regardez ce qui se passe quand vous demandez : « fish » est-il dans cette liste ?

# Membership check — is "fish" in the list?
if "fish" in pets:    # scans ["cat", "dog", "cat", "fish"...] one by one
    print("found!")   # had to check 4 items before finding it

L'ordinateur vérifie « cat » — non. « dog » — non. « cat » — non. « fish » — oui ! Il a scanné 4 éléments pour trouver la réponse. Avec 1 000 éléments, il pourrait scanner les 1 000. Cette vérification d'appartenance coûte O(N).

Prédire le coût du scan

Vous avez une liste de 500 noms d'étudiants dans un ordre aléatoire.

Vous voulez vérifier si « Zara » apparaît dans la liste.

students = ["Alex", "Maria", ..., "Zara", ...]  # 500 names
if "Zara" in students:
    print("enrolled")
Quel est le meilleur cas, le pire cas, & le nombre moyen de noms que l'ordinateur vérifie pour trouver « Zara » ? Quelle classe Big O décrit cette opération ? Expliquez votre raisonnement.

Comment les ensembles fonctionnent différemment

Ensembles : conçus pour les questions d'appartenance

Un ensemble (set) stocke des éléments uniques en utilisant une technique appelée hachage. Quand vous ajoutez un élément, l'ordinateur calcule un hachage (une empreinte numérique) qui lui indique exactement où stocker cet élément.

Quand vous vérifiez l'appartenance, l'ordinateur calcule le même hachage & saute directement au bon endroit. Pas de scan.

Liste vs ensemble : comment les données vivent en mémoire — liste fait un scan, ensemble saute

# A set of pets
pets = {"cat", "dog", "cat", "fish", "dog"}

print(pets)      # {"cat", "dog", "fish"} — duplicates removed
print(len(pets)) # 3 — only unique items

Remarquez trois différences par rapport aux listes :

1. Pas de doublons. « cat » apparaissait deux fois dans l'entrée mais l'ensemble en garde une seule copie.

2. Pas d'ordre garanti. L'ensemble pourrait afficher les éléments dans n'importe quel arrangement.

3. Pas d'accès par index. Vous ne pouvez pas écrire pets[0] — les ensembles n'ont pas de positions.

L'échange : vous perdez l'ordre & les doublons. Vous gagnez O(1) test d'appartenance — vérifier si un élément existe prend le même temps qu'il y ait 5 éléments ou 5 millions.

# Membership check — is "fish" in the set?
if "fish" in pets:    # hash("fish") → jump to bucket → found
    print("found!")   # checked exactly 1 spot, not 4

Ensemble vs Liste d'appartenance

Vous avez 500 noms d'étudiants stockés dans un ensemble au lieu d'une liste.

students = {"Alex", "Maria", ..., "Zara", ...}  # 500 names
if "Zara" in students:
    print("enrolled")
Combien d'éléments l'ordinateur vérifie-t-il pour déterminer si « Zara » existe dans un ensemble de 500 noms ? Quelle classe Big O décrit cela ? Pourquoi cela diffère-t-il de la version liste ?

Comparaison côte à côte

Feuille de triche des opérations

Coûts des opérations : liste vs ensemble — appartenance, ajout, index, déduplication

Utilisez une liste quand vous avez besoin de :

- Éléments dans un ordre spécifique (une playlist, une recette, une file d'attente)

- Accès par position (items[3])

- Doublons préservés (« cat » apparaît 3 fois = 3 chats)

Utilisez un ensemble quand vous avez besoin de :

- Vérifications d'appartenance rapides (« ai-je déjà vu ceci ? »)

- Suppression automatique des doublons

- Pas besoin de l'ordre

Utilisez les deux quand vous avez besoin d'ordre ET recherche rapide :

seen = set()     # O(1) membership checks
result = []      # preserves insertion order
for item in data:
    if item not in seen:  # O(1) check
        seen.add(item)    # O(1) add
        result.append(item)  # O(1) append
# Total: O(N) — one pass, each step O(1)

Ce motif « ensemble + liste » vous donne le meilleur des deux : résultats ordonnés avec détection rapide des doublons.

Choisir la bonne structure

Trois problèmes. Pour chacun, décidez : liste, ensemble, ou les deux ?

1. Vous devez stocker une playlist de chansons dans l'ordre où l'utilisateur les a ajoutées.

2. Vous devez vérifier rapidement si un nom d'utilisateur a déjà été pris.

3. Vous devez supprimer les e-mails en doublons d'une liste de diffusion mais les garder dans l'ordre où ils se sont inscrits.

Pour chacun des trois problèmes, quelle structure de données devriez-vous utiliser (liste, ensemble, ou les deux) ? Expliquez pourquoi pour chacun.

Le problème de déduplication

Problème : Supprimer les doublons, garder l'ordre

Vous recevez une liste d'inscriptions par e-mail. Certaines personnes se sont inscrites deux fois. Vous devez envoyer un e-mail par personne, dans l'ordre où elles se sont inscrites en premier.

Tentative 1 : liste uniquement (lent)

signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
unique = []
for email in signups:
    if email not in unique:  # O(N) scan of unique list
        unique.append(email)
# Works! But: O(N) check × N items = O(N²)

Avec 100 inscriptions, cela fait ~5 000 vérifications. Avec 10 000 inscriptions : ~50 000 000 vérifications.

Tentative 2 : ensemble + liste (rapide)

signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
seen = set()
unique = []
for email in signups:
    if email not in seen:   # O(1) hash lookup
        seen.add(email)     # O(1) add to set
        unique.append(email) # O(1) append to list
# O(1) check × N items = O(N)

Même résultat. Même ordre. Mais : 10 000 inscriptions nécessitent maintenant ~10 000 vérifications au lieu de ~50 000 000.

Calculer l'accélération

La version liste uniquement s'exécute en O(N²). La version ensemble+liste s'exécute en O(N).

Vous avez 10 000 inscriptions par e-mail à dédupliquer.

Combien de vérifications approximatives chaque version effectue à N=10 000 ? Combien de fois plus rapide est la version ensemble+liste ? Montrez votre calcul.

Trouver les éléments communs

Problème : Trouver les éléments dans les deux listes

Vous avez deux listes de classe. Vous devez trouver les étudiants inscrits dans les deux classes.

Approche lente : boucles imbriquées O(N × M)

class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b = ["Bob", "Diana", "Eve", "Frank"]
both = []
for student in class_a:        # N iterations
    if student in class_b:     # M scans each time
        both.append(student)
# O(N × M) — each student scanned against all of class_b

Approche rapide : convertir une liste en ensemble O(N + M)

class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b_set = set(class_b)     # O(M) to build
both = []
for student in class_a:        # N iterations
    if student in class_b_set: # O(1) each time
        both.append(student)
# O(N + M) — build the set once, check N times at O(1) each

Ou même plus simple en utilisant l'intersection d'ensemble intégrée de Python :

both = set(class_a) & set(class_b)  # O(N + M)

L'opérateur & trouve tous les éléments qui apparaissent dans les deux ensembles.

Réécrire pour la vitesse

Une école a deux clubs avec 200 membres chacun. Ce code trouve les étudiants dans les deux clubs :

chess_club = [...]   # 200 names
art_club = [...]     # 200 names
both_clubs = []
for student in chess_club:
    if student in art_club:
        both_clubs.append(student)
Quel est le Big O de ce code ? Réécrivez-le pour s'exécuter plus vite en utilisant un ensemble. Quel est le Big O de votre version améliorée ? Expliquez la différence.

Votre cadre de décision

Votre cadre de décision de structure de données

Chaque fois que vous écrivez du code qui stocke une collection d'éléments, demandez-vous :

1. Ai-je besoin d'éléments dans l'ordre ? → liste

2. Ai-je besoin de vérifications d'appartenance rapides ? → ensemble

3. Ai-je besoin à la fois d'ordre ET de vérifications rapides ? → ensemble + liste ensemble

4. Ai-je besoin de doublons ? → liste (les ensembles supprimaient les doublons)

---

L'idée clé de cette leçon : le même programme, résolvant le même problème, peut s'exécuter 1 000× à 1 000 000× plus vite juste en choisissant la bonne structure de données. Pas de trucs astucieux. Pas de math compliquée. Juste en demandant : quelles opérations mon code fait-il le plus souvent ? Puis en choisissant la structure où ces opérations coûtent O(1) au lieu de O(N).

Cette leçon couvrait les listes & ensembles. D'autres structures de données existent (dictionnaires, arbres, tas) qui résolvent d'autres problèmes. Le cadre de décision reste le même : comprenez vos opérations, puis choisissez la structure qui les rend bon marché.

---

Voulez-vous approfondir ? Notre leçon Big O couvre les taux de croissance & les calculs d'évolution en détail. Voir Big O : à quelle vitesse c'est assez rapide ?. Notre cours unhamming couvre le défaut du monde réel (MOAD-0001) où ce correctif exact liste-à-ensemble a produit une accélération 1 000× dans le logiciel en production. Voir Le chapitre manquant : complexité algorithmique.

Déboguer ce code

Un étudiant a écrit ce code pour compter combien de mots uniques apparaissent dans un livre :

words = book.split()           # list of all words
unique_count = 0
checked = []
for word in words:             # N words
    if word not in checked:    # scans checked list
        checked.append(word)
        unique_count += 1
print(unique_count)

Le livre a 100 000 mots.

Quel est le Big O de ce code ? Pourquoi s'exécute-t-il lentement sur un grand livre ? Réécrivez-le pour résoudre le même problème en O(N). Ensuite expliquez : pourriez-vous résoudre cela en une ligne en utilisant un ensemble ? À quoi cela ressemblerait-il ?