16  Les ensembles

Les ensembles (set en Python) sont des collections sans doublons et non ordonnées. Moins connus que les listes et dicts, ils sont pourtant extrêmement utiles pour deux cas : supprimer les doublons et faire des opérations ensemblistes (union, intersection, différence). Le TOSA les teste surtout sur ces deux usages.

16.1 Qu’est-ce qu’un ensemble ?

Un ensemble est une collection d’éléments uniques et non ordonnés. On le crée avec des accolades {} comme un dictionnaire, mais sans paires clé: valeur.

couleurs = {"rouge", "vert", "bleu"}
nombres = {1, 2, 3, 4, 5}

print(couleurs)
print(nombres)
print(type(couleurs))
{'bleu', 'rouge', 'vert'}
{1, 2, 3, 4, 5}
<class 'set'>

Quatre caractéristiques :

  1. Éléments uniques : les doublons sont automatiquement supprimés.
  2. Non ordonnés : pas d’indice, l’ordre n’est pas garanti.
  3. Modifiables : on peut ajouter et retirer des éléments.
  4. Éléments immuables uniquement : comme pour les clés de dict.

Les doublons sont supprimés automatiquement

s = {1, 2, 2, 3, 3, 3, 4}
print(s)                 # {1, 2, 3, 4}
{1, 2, 3, 4}

Pas d’indice possible

s = {10, 20, 30}
print(s[0])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 2
      1 s = {10, 20, 30}
----> 2 print(s[0])

TypeError: 'set' object is not subscriptable

Ensemble vide : piège !

⚠️ Piège TOSA : ensemble vide

{} crée un dictionnaire vide, pas un ensemble vide ! Pour un ensemble vide, il faut utiliser set() :

dict_vide = {}
ens_vide = set()

print(type(dict_vide))
print(type(ens_vide))
<class 'dict'>
<class 'set'>

Créer un ensemble à partir d’une autre collection

# Depuis une liste (supprime les doublons automatiquement)
nombres = set([1, 2, 2, 3, 3, 4])
print(nombres)

# Depuis une chaîne (supprime les doublons de caractères)
lettres = set("banana")
print(lettres)
{1, 2, 3, 4}
{'b', 'a', 'n'}

16.2 Les méthodes principales

Ajouter et retirer

Méthode Effet
add(x) Ajoute x (sans effet si déjà présent)
remove(x) Retire x, lève KeyError si absent
discard(x) Retire x, pas d’erreur si absent
pop() Retire et renvoie un élément arbitraire
clear() Vide l’ensemble
s = {1, 2, 3}

s.add(4)
print(s)

s.add(2)                # déjà présent : sans effet
print(s)

s.remove(1)
print(s)

s.discard(99)           # pas d'erreur même si absent
print(s)
{1, 2, 3, 4}
{1, 2, 3, 4}
{2, 3, 4}
{2, 3, 4}
⚠️ remove vs discard
s = {1, 2, 3}
s.remove(99)            # KeyError : 99
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[7], line 2
      1 s = {1, 2, 3}
----> 2 s.remove(99)            # KeyError : 99

KeyError: 99
s = {1, 2, 3}
s.discard(99)           # pas d'erreur
print(s)
{1, 2, 3}

Si vous ne savez pas si l’élément est présent, préférez discard.

Tester l’appartenance : in

Le test in est extrêmement rapide sur un set (complexité O(1)) — bien plus rapide qu’une liste.

ens = {1, 2, 3, 4, 5}
print(3 in ens)       # True
print(99 in ens)      # False
True
False
Astuce performance

Si vous devez tester plusieurs fois l’appartenance d’éléments à une collection, convertissez-la en set d’abord :

# ❌ Lent sur une grande liste
grande_liste = list(range(1_000_000))
if 500_000 in grande_liste:
    ...

# ✅ Rapide
grand_set = set(grande_liste)
if 500_000 in grand_set:
    ...

Cette optimisation est souvent testée au niveau Opérationnel et Avancé du TOSA.

16.3 Les opérations ensemblistes (le cœur des ensembles)

C’est ce qui fait la valeur unique des sets : effectuer des opérations mathématiques d’ensembles.

Union — tout, sans doublons

Opérateur Méthode équivalente
a \| b a.union(b)
a = {1, 2, 3}
b = {3, 4, 5}

print(a | b)
print(a.union(b))
{1, 2, 3, 4, 5}
{1, 2, 3, 4, 5}

Intersection — éléments communs

Opérateur Méthode équivalente
a & b a.intersection(b)
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

print(a & b)
print(a.intersection(b))
{3, 4}
{3, 4}

Différence — éléments de a qui ne sont pas dans b

Opérateur Méthode équivalente
a - b a.difference(b)
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

print(a - b)           # {1, 2} — dans a mais pas dans b
print(b - a)           # {5, 6} — dans b mais pas dans a
{1, 2}
{5, 6}

Différence symétrique — éléments dans l’un OU l’autre, mais pas les deux

Opérateur Méthode équivalente
a ^ b a.symmetric_difference(b)
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

print(a ^ b)           # {1, 2, 5, 6} — sans les éléments communs
{1, 2, 5, 6}

Tableau récapitulatif

Opération Opérateur Méthode Résultat
Union a \| b a.union(b) Tout
Intersection a & b a.intersection(b) Ce qui est commun
Différence a - b a.difference(b) Dans a uniquement
Diff. symétrique a ^ b a.symmetric_difference(b) Pas en commun

Tests ensemblistes

Méthode Effet
a.issubset(b) ou a <= b a est-il inclus dans b ?
a.issuperset(b) ou a >= b a contient-il b ?
a.isdisjoint(b) a et b n’ont aucun élément commun ?
a = {1, 2}
b = {1, 2, 3, 4}
c = {10, 20}

print(a.issubset(b))       # True
print(b.issuperset(a))     # True
print(a.isdisjoint(c))     # True
print(a.isdisjoint(b))     # False (ils partagent 1 et 2)
True
True
True
False

16.4 Cas d’usage typique : supprimer les doublons

L’usage #1 des sets dans du code Python réel.

# À partir d'une liste avec doublons
notes = [12, 15, 12, 9, 15, 17, 12, 9]

notes_uniques = set(notes)
print(notes_uniques)

# Si on veut RÉCUPÉRER une liste sans doublons
liste_sans_doublons = list(set(notes))
print(liste_sans_doublons)
{9, 12, 17, 15}
[9, 12, 17, 15]
⚠️ set() ne préserve pas l’ordre

Contrairement aux dicts (depuis 3.7), les ensembles ne garantissent pas l’ordre d’insertion.

Si vous avez besoin de supprimer les doublons en préservant l’ordre, utilisez plutôt :

notes = [12, 15, 12, 9, 15, 17, 12, 9]

# Astuce : les dicts préservent l'ordre, les clés sont uniques
notes_uniques_ordonnees = list(dict.fromkeys(notes))
print(notes_uniques_ordonnees)
[12, 15, 9, 17]

Cette astuce est souvent demandée en entretien !

16.5 Les frozenset : ensembles immuables

Python propose un type frozenset : un set immuable, comme un tuple est une liste immuable. On l’utilise rarement au niveau Basique, mais il est bon de savoir qu’il existe.

f = frozenset([1, 2, 3])
print(f, type(f))
frozenset({1, 2, 3}) <class 'frozenset'>
f.add(4)     # ❌ impossible, immuable
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[18], line 1
----> 1 f.add(4)     # ❌ impossible, immuable

AttributeError: 'frozenset' object has no attribute 'add'

À la différence d’un set classique, un frozenset peut servir de clé de dictionnaire ou d’élément dans un autre set.

16.6 Vue synthétique : les 4 structures de données de base

Tableau essentiel pour le TOSA — à connaître par cœur :

Caractéristique list tuple dict set
Syntaxe [1, 2] (1, 2) {"a": 1} {1, 2}
Ordonné ✅ (3.7+)
Modifiable
Indexable ❌ (par clé)
Doublons autorisés ❌ (clés)
Accès l[i] t[i] d[k] x in s
Test in complexité O(n) O(n) O(1) O(1)
Principaux usages Collection générique Données immuables Données structurées Unicité, ensembles

🧩 Quiz 6.2 — Les ensembles

Question 1

Que renvoie {1, 2, 2, 3, 3, 3} ?

  1. {1, 2, 2, 3, 3, 3}
  2. {1, 2, 3}
  3. [1, 2, 3]
  4. Une erreur

b) {1, 2, 3} — les doublons sont automatiquement supprimés à la création d’un set.

print({1, 2, 2, 3, 3, 3})
{1, 2, 3}

Question 2

Que vaut type({}) ?

  1. set
  2. dict
  3. tuple
  4. list

b) dictpiège classique ! {} crée un dictionnaire vide, pas un ensemble vide. Pour un set vide : set().

print(type({}))         # dict
print(type(set()))      # set
print(type({1, 2}))     # set (si non vide, ça marche)
<class 'dict'>
<class 'set'>
<class 'set'>

Question 3

Que renvoie {1, 2, 3} | {3, 4, 5} ?

  1. {3}
  2. {1, 2, 4, 5}
  3. {1, 2, 3, 4, 5}
  4. Une erreur

c) {1, 2, 3, 4, 5} — l’opérateur | fait l’union (tous les éléments, sans doublons).

print({1, 2, 3} | {3, 4, 5})
{1, 2, 3, 4, 5}

Question 4

Que renvoie {1, 2, 3} & {2, 3, 4} ?

  1. {1, 2, 3, 4}
  2. {2, 3}
  3. {1, 4}
  4. {2, 3, 4}

b) {2, 3} — l’opérateur & fait l’intersection (éléments communs).

print({1, 2, 3} & {2, 3, 4})
{2, 3}

Question 5

Que vaut len(set([1, 2, 2, 3, 3, 3])) ?

  1. 3
  2. 6
  3. 5
  4. 0

a) 3 — la conversion en set supprime les doublons : {1, 2, 3}, donc longueur 3.

print(len(set([1, 2, 2, 3, 3, 3])))
3

Question 6

Que s’affiche-t-il ?

s = {1, 2, 3}
s.discard(99)
print(s)
  1. {1, 2, 3}
  2. KeyError
  3. {1, 2, 3, 99}
  4. None

a) {1, 2, 3}discard() ne lève pas d’erreur si l’élément est absent. Contrairement à remove() qui aurait levé KeyError.

s = {1, 2, 3}
s.discard(99)
print(s)
{1, 2, 3}

Question 7

Quelle est la différence principale entre set et list ?

  1. Les sets sont plus lents pour in
  2. Les sets n’acceptent pas les doublons et ne sont pas ordonnés
  3. Les sets sont immuables
  4. Les sets n’acceptent que des nombres

b) Les sets n’acceptent pas les doublons et ne sont pas ordonnés — c’est leur caractéristique distinctive. Ils acceptent tous les types immuables (pas que des nombres), sont modifiables (pas immuables), et in y est plus rapide que sur une liste.


✏️ Exercice 8.1 — Dédoublonner une liste

Soit la liste fruits = ["pomme", "banane", "pomme", "cerise", "banane", "kiwi"].

Créez une liste fruits_uniques contenant chaque fruit une seule fois.

fruits = ["pomme", "banane", "pomme", "cerise", "banane", "kiwi"]

# Créer fruits_uniques sans doublons
fruits_uniques = ...

print(fruits_uniques)
fruits = ["pomme", "banane", "pomme", "cerise", "banane", "kiwi"]

fruits_uniques = list(set(fruits))
print(fruits_uniques)
['banane', 'pomme', 'cerise', 'kiwi']

Attention : l’ordre est perdu !

Utilise dict.fromkeys() (vu plus haut) :

fruits = ["pomme", "banane", "pomme", "cerise", "banane", "kiwi"]

fruits_uniques = list(dict.fromkeys(fruits))
print(fruits_uniques)     # ordre préservé
['pomme', 'banane', 'cerise', 'kiwi']

✏️ Exercice 8.2 — Étudiants en commun

Deux classes ont leur liste d’étudiants :

classe_A = {"Alice", "Bob", "Charlie", "Diana", "Eve"}
classe_B = {"Charlie", "Diana", "Frank", "Grace"}

Écrivez un programme qui affiche :

  1. Les étudiants dans les deux classes.
  2. Les étudiants dans la classe A uniquement.
  3. Les étudiants dans la classe B uniquement.
  4. Les étudiants dans l’une ou l’autre, mais pas les deux.
  5. Le total d’étudiants distincts.
classe_A = {"Alice", "Bob", "Charlie", "Diana", "Eve"}
classe_B = {"Charlie", "Diana", "Frank", "Grace"}

# Répondre aux 5 questions en utilisant des opérations ensemblistes
classe_A = {"Alice", "Bob", "Charlie", "Diana", "Eve"}
classe_B = {"Charlie", "Diana", "Frank", "Grace"}

# 1. Dans les deux : intersection
print("Dans les deux :", classe_A & classe_B)

# 2. Seulement dans A : différence A - B
print("Seulement A :", classe_A - classe_B)

# 3. Seulement dans B : différence B - A
print("Seulement B :", classe_B - classe_A)

# 4. Dans l'un OU l'autre mais pas les deux : différence symétrique
print("Exclusifs :", classe_A ^ classe_B)

# 5. Total distinct : union
print("Total distinct :", len(classe_A | classe_B))
Dans les deux : {'Charlie', 'Diana'}
Seulement A : {'Alice', 'Bob', 'Eve'}
Seulement B : {'Grace', 'Frank'}
Exclusifs : {'Alice', 'Bob', 'Grace', 'Eve', 'Frank'}
Total distinct : 7

✏️ Exercice 8.3 — Lettres uniques dans un mot

Écrivez une fonction lettres_uniques(mot) qui renvoie l’ensemble des lettres présentes dans un mot (sans doublons, en minuscules).

Exemple : lettres_uniques("Banana"){"b", "a", "n"}.

def lettres_uniques(mot):
    ...

print(lettres_uniques("Banana"))
print(lettres_uniques("Mississippi"))
print(lettres_uniques("Python"))
def lettres_uniques(mot):
    return set(mot.lower())

print(lettres_uniques("Banana"))
print(lettres_uniques("Mississippi"))
print(lettres_uniques("Python"))
{'b', 'a', 'n'}
{'i', 's', 'p', 'm'}
{'y', 't', 'p', 'h', 'n', 'o'}
def nb_lettres_distinctes(mot):
    return len(set(mot.lower()))

for mot in ["Python", "Mississippi", "abécédaire"]:
    print(f"{mot}: {nb_lettres_distinctes(mot)} lettres distinctes")
Python: 6 lettres distinctes
Mississippi: 4 lettres distinctes
abécédaire: 8 lettres distinctes
def memes_lettres(mot1, mot2):
    """Deux mots utilisent-ils exactement les mêmes lettres ?"""
    return set(mot1.lower()) == set(mot2.lower())

print(memes_lettres("listen", "silent"))   # True (anagramme)
print(memes_lettres("python", "chaton"))   # False
True
False

À retenir

Points clés du chapitre
  1. Un set est une collection non ordonnée d’éléments uniques et immuables.
  2. Piège : {} est un dict vide, pas un set vide. Utilisez set().
  3. add, remove (erreur si absent), discard (sans erreur).
  4. Test in en O(1) → bien plus rapide qu’une liste pour les gros volumes.
  5. Opérations ensemblistes : | union, & intersection, - différence, ^ diff. symétrique.
  6. Usage roi : list(set(liste)) pour dédoublonner (ordre perdu).
  7. Pour dédoublonner en gardant l’ordre : list(dict.fromkeys(liste)).
  8. Les sets n’acceptent que des éléments immuables (chaînes, nombres, tuples).

← Chapitre précédent : Les dictionnairesChapitre suivant : Les boucles →