Aller au contenu

1NSI : cours Ensembles Python⚓︎

Introduction⚓︎

Def

Un ensemble 🇫🇷, ou un set 🇬🇧, est un conteneur d'éléments :

  • NON ORDONNÉS (en particulier : PAS d'indices)
  • UNIQUES (donc PAS de doublons)

C'est une structure de données primitive de Python, qui est un type construit donc un conteneur/collection d'éléments.

Un ensemble / set en Python, modélise la notion d'ensemble en Mathématiques.

Les types des éléments peuvent :

  • être variables, pour certains langages, dits à typage dynamique (Python, ..),
  • ou pas, pour certains autres langages, dits à typage statique (langage C, Java, etc..)

Définir un Ensemble avec des {}⚓︎

On définit un ensemble de type set, avec des accolades {}

# On peut mettre des nombres dans un ensemble
>>> s = {3,4,5}
# et même : un mélange de plusieurs types de nombres
>>> s = {3,4,5.2}
# voire : un mélange de nombres, et d'autres types
>>> s = {2,3,7,5.4,"Bonjour"}
>>> print(s)
{2, 3, 5.4, 'Bonjour', 7}

# Classiquement: on met des lettres
>>> s = {'a','b','j','m','n','o','r','u'}
# On ne prend PAS en compte les répétitions !
# ni l'ordre (alphabétique, ou croissant, etc..)
>>> s = {'a','b','c','a','b','a','b','c'}
>>> print(s)
{'b', 'a', 'c'}

Quels éléments peut contenir un ensemble ?

  • Un ensemble peut contenir des éléments de type int, float, str, tuple Plus généralement, un ensemble doit contenir des objets hashables (cf définition ci-très-en-dessous) :
  • Sont hashables, donc peuvent appartenir à un ensemble, les types : int, float, str, tuple
  • Ne sont PAS hashables, donc ne peuvent PAS appartenir à un ensemble, les types : list, set, dict

ATTENTION : ERREUR CLASSIQUE la notation {} ne désigne PAS l'ensemble vide (cette notation sera réservée pour le dictionnaire vide)

Remarque : Les ensembles / set ne peuvent pas contenir des sous-ensembles / set. Pour représenter des set de set les set intérieurs doivent être des frozenset. Les frozenset sont un type de données très semblable au type set, mais qui a pour différence notable par rapport aux set qu'ils sont immutable et hashables, il peut ainsi être utilisé comme clef de dictionnaire ou élément d'un autre set.

Définir un ensemble avec set()⚓︎

Il est fréquent d'utiliser des caractères comme éléments d'un ensemble. Pour simplifier la définition de tels ensembles, on peut utiliser le constructeur de type set() pour définir un ensemble sans répétition des caractères rencontrés:

>>> s = set("abracadabra")
>>> print(s)
{'r', 'c', 'd', 'a', 'b'}

# ATTENTION aux ESPACES, aux ACCENTS, à la CASSE, aux caractères inusuels, etc
>>> s = set("Bonjour mère, je vais bien")
>>> print(s)
{'r', ',', 'B', ' ', 'e', 'j', 's', 'b', 'è', 'n', 'm', 'a', 'u', 'v', 'o', 'i'}

Remarque : la notation set() permet de définir l'ensemble vide

Plus généralement, on peut générer un ensemble grâce à set(iterable)iterable est un itérable (chaînes, listes, tuples, dictionnaires, fichiers, etc..)

# une liste et/ou un tuple sont des itérables
>>> s = set([1,6,4,6,4,4])
>>> print(s)
{1,6,4}

>>> s = set((1,6,4,6,4,4))
>>> print(s)
{1,6,4}

Fonctions utiles sur les Ensembles⚓︎

Longueur d'un ensemble⚓︎

>>> s = set("abracadabra")
>>> print(s)
{'r', 'c', 'd', 'a', 'b'}
>>> len(s)  # renvoie le nombre d'éléments/objets de l'ensemble
5

len renvoie la longueur (/le cardinal en mathématiques) de l'ensemble s

Ensemble Vide⚓︎

# set() désigne l'ENSEMBLE VIDE
>>> s = set()
>>> len(s)
0   # la longueur de l'ensemble vide vaut 0

ATTENTION : ERREUR CLASSIQUE \(\,\) {} représente le dictionnaire vide, mais PAS l'ensemble vide

Appartenance à un Ensemble avec in, ou pas, avec not in⚓︎

>>> s = set("abracadabra")
>>> s
{'r', 'c', 'd', 'a', 'b'}
>>> 'a' in s
True
>>> 'e' in s
False
>>> {'c','d'} in s
False
# ou bien NON appartenance avec les mots-clés `not in`
>>> 'a' not in s
False
# les ensembles ne peuvent PAS contenir de sous-ensembles
s = {'r', {'c', 'd'}, 'a', 'b'}
TypeError: unhashable type: 'set'

Indices / Index⚓︎

NOOOOON ! On a dit PAS d'indices (car pas d'ordre) sur les ensembles. donc PAS de tranches/slices non plus sur les ensembles.

Les Ensembles NE sont PAS des Séquences⚓︎

Les Ensembles disposent bien d'une fonction len(), mais PAS d'indices entiers: en particulier ils ne sont pas ordonnés (contrairement aux séquences), donc :

Pte

Les ensembles set NE sont PAS des séquences.

Techniques de parcours d'Ensembles⚓︎

Technique 1 : élément par élément, SANS indices⚓︎

s=set('abracadabra')
for element in s:
  print(element)

Pte

Les ensembles set sont des itérables.

Technique 2 : élément par élément, AVEC des indices⚓︎

Cette technique N'existe PAS : car pas d'indices...

Méthodes sur les Ensembles⚓︎

Méthodes usuelles sur les Ensembles⚓︎

Notons s1 et s2 deux ensembles. s1={1,2} et s2={2,4,6,8}

  • Appartenance :
    • x in s1 renvoie True si l'élément x appartient à l'ensemble, ou False sinon.
    • x not in s1 renvoie True si x n'est PAS dans s1, et False sinon
  • Ajout d'éléments :
    • s1.add(x) ajoute l'élément x à l'ensemble s1. Ex : s1.add(3) ajoute 3 à l'ensemble s1 ⚠ ATTENTION ⚠ (syntaxe mutable) : ne PAS écrire s1 = s1.add(x) qui écrase totalement la variable s1 (après quoi : s1 vaut None) Par contre, cette méthode ne permet d'ajouter qu'un seul élément à la fois. Pour ajouter plusieurs éléments à la fois :
    • s1.union(iterable) renvoie les éléments de s1 auxquels on a ajouté tous les éléments de l'iterable mais sans modifier s1. Ex : s1.union({4,5}) renvoie {1,2,3,4,5}
    • Union \(\cup\) : s1 | s2 \(\quad\) ( symbole | = AltrGr+6) renvoie l'union de s1 et s2, mais sans modifier s1. renvoie les éléments de l'ensemble s1 auxquels on a ajouté tous ceux de l'ensemble s2, mais sans modifier s1 Notation | = : s1 | = s2 signifie s1 = s1 | s2
    • s1.update(iterable) ajoute tous les éléments de l'iterable à l'ensemble s1. Ex : s1.update([6,7]) : s1 vaut {1,2,3,4,5,6,7}
  • Suppression d'éléments :
    • s1.remove(x) supprime l'élément existant x de l'ensemble s1 ⚠ ATTENTION ⚠ : lève une exception KeyError si l'élément x est inexistant dans l'ensemble s1 : Ex : s1.remove(10) renvoie KeyError: 10
    • s1.discard(x) supprime l'élément x de l'ensemble s1 si x appartient à s1, mais ne lève pas d'erreur en cas d'inexistance de x dans s1. Ex : s1.discard(10) supprime 10 de s1 lorsque c'est possible (si 10 appartient à s1, sinon ne fait rien)
    • s1.pop() retire et renvoie un élément arbitraire/aléatoire de l'ensemble s1. ⚠ ATTENTION ⚠ : Lève une exception KeyError si l'ensemble est vide.
    • s1.clear() supprime tous les éléments du set
  • Différence/Soustraction d'ensembles:
    • Différence avec un itérable: s1.difference(iterable) renvoie la soustraction / différence de s1 par l'iterable, c'est-à-dire les éléments de s1 qui ne sont PAS dans iterable, mais sans modifier s1.
    • Différence \(-\) : s1 - s2 \(\,\) renvoie la différence de s1 par s2, mais sans modifier s1. Ex : {1,2,3,4,5,6,7} - {2,4,6,8} = {1,3,5,7} Notation -= : s1 -= s2 signifie s1 = s1 - s2
    • Différence Symétrique avec un itérable : s1.symmetric_difference(iterable) renvoie la différence symétrique de s1 par iterable, c'est-à-dire les éléments qui sont :
      • soit dans s1,
      • soit dans iterable,
      • mais pas dans les deux simultanément.
    • Différence Symétrique \(\Delta\) : s1 ^ s2 \(\,\) ou s1.__xor__(s2) renvoie la différence symétrique de s1 par s2, mais sans modifier s1. Ex : {2,3,4,5} ^ {2,3,8} = {4,5,8} Notation ^= : s1 ^= s2 signifie s1 = s1 ^ s2
  • Duplication :
    • s2 = s1.copy() copie l'ensemble s1 dans s2
  • Intersections :
    • Intersection avec un itérable : s1.intersection(iterable)
      • renvoie l'intersection de s1 avec iterable, c'est-à-dire les éléments de s1 qui appartiennent également à iterable.
      • Remarque: renvoie set() lorsque l'intersection est vide.
    • Intersection \(\cap\) : s1 & s2 renvoie l'intersection entre s1 et s2, mais sans modifier s1 Ex : {2, 3, 4, 5} & {2,3,8} renvoie {2,4} Notation &= : s1 &= s2 signifie s1 = s1 & s2
    • s1.isdisjoint(iterable)
      • renvoie True lorsque l'ensemble s1 est disjoint de iterable (pas d'élément en commun), ou
      • False sinon (au moins un élément commun)
  • Inclusions :
    • Inclusion d'un itérable : s2.issubset(iterable)
      • renvoie True si l'ensemble s2 est inclus ou égal à iterable (/est un sous-ensemble de),
      • ou False sinon
    • Inclusion \(\subseteq\) : s2 < = s1
      • renvoie True si l'ensemble s2 est inclus ou égal à l'ensemble s1
      • False sinon
    • Inclusion stricte \(\subset\) : s2 < s1
      • renvoie True si l'ensemble s2 est strictement inclus dans l'ensemble s1
      • False sinon
    • Contient un itérable : s2.issuperset(iterable)
      • renvoie True si l'ensemble s2 contient ou est égal à iterable (/est un sur-ensemble de),
      • ou False sinon
    • Contient \(\supseteq\) : s2 > = s1
      • renvoie True si l'ensemble s2 contient ou est égal à l'ensemble s1
      • False sinon
    • Contient strictement \(\supset\) : s2 > s1
      • renvoie True si l'ensemble s2 contient strictement l'ensemble s1
      • False sinon

Liste Complète des Méthodes sur les Ensembles⚓︎

Aide en ligne⚓︎

Vous trouverez une Liste Complète de Méthodes opérant sur les Ensembles, sur : * cette page de la Documentation Officielle * ou sur cette autre page de la Documentation Officielle

Aide en Local (dans un Interpréteur Python)⚓︎

  1. Dans un interpréteur Python, dir(set) affiche la liste complète de toutes les méthodes disponibles sur les set, y compris les méthodes magiques/spéciales (cf ci-dessous), mais elles ne sont pas documentées (ni signature, ni docstring).

  2. Dans un interpréteur Python, help(set) affiche la liste complète de toutes les méthodes disponibles sur les set, y compris les méthodes magiques/spéciales (cf ci-dessous), AVEC DOCUMENTATION: AVEC LEURS SIGNATURES ET LES DOCSTRINGS.

Méthodes magiques / Méthodes spéciales sur les Ensembles⚓︎

Méthodes magiques / Méthodes spéciales sur les Ensembles

Parmi toutes les méthodes disponibles affichées par dir(set), certaines sont encadrées par deux underscores (de chaque côté) __unCertainNom__() : Elles sont appelées des méthodes magiques ou méthodes spéciales sur les ensembles. En pratique cela signifie que :

  • elles sont accessibles via la syntaxe normale pour les méthodes : nomEnsemble.__nomMethodeMagique__()
  • elles sont également accessibles via une syntaxe spéciale / magique (qui dépend de la méthode en question)

de Méthodes magiques / Méthodes spéciales sur les Ensembles

On se donne deux listes s1={1,2,3,4} et s2={5,6,7}

  • __len()__ : calcule la longueur d'un ensemble ...
    • Syntaxe normale : s2.__len__() renvoie le nombre \(3\)
    • Syntaxe spéciale : len(s2) renvoie le nombre \(3\)
  • __eq()__ : teste l'égalité entre deux ensembles ...
    • Syntaxe normale : s1.__eq__(s2) renvoie False car s1 et s2 ne sont pas égaux
    • Syntaxe spéciale : s1 == s2 renvoie False (pour les mêmes raisons) Principe Général : À chaque fois qu'on veut tester l'égalité entre deux ensembles avec le symbole ==, c'est en fait la méthode magique __eq__() qui est appelée pour tester l'égalité.

Voici quelques autres méthodes magiques sur les listes :

  • __ne__() veut dire \(\ne\) : "Not Equal to" c'est-à-dire Non égal, donc ! = en Python
  • __gt__() veut dire \(\gt\) : "Greater Than" c'est-à-dire Supérieur Strictement
  • __ge__() veut dire \(\ge\) : "Greater than or Equal to" c'est-à-dire Supérieur ou égal à
  • __lt__() veut dire \(\lt\) : "Less Than" c'est-à-dire Inférieur Strictement
  • __le__() veut dire \(\le\) : "Less than or Equal to" c'est-à-dire Inférieur ou égal
  • __contains__() correspond au mot-clé in utilisé pour tester l'inclusion d'un ensemble dans un autre
  • __or__() veut dire union ou bien ou ou bien | pour réaliser l'union de deux ensembles

  • ⚠ __repr__() ⚠ représente une ensemble dans un interpréteur Python, c'est-à-dire qu'il affiche un ensemble dans un interpréteur Python, sous un certain format spécifique. Elle est appelée quand on tape dans l'interpréteur :

    • ou bien >>> s1 \(\quad\) (où s1 désigne le nom d'un ensemble)
    • ou bien >>> print(s1)
  • ⚠ __str__() ⚠ représente un ensemble dans un interpréteur Python, c'est-à-dire qu'il affiche un ensemble dans un interpréteur Python, sous un certain format spécifique, mais seulement pour le print()
    • >>> print(s1)
  • etc...

Opérations Arithmétiques sur les Ensembles⚓︎

Addition⚓︎

  • PAS d'addition + entre deux ensembles.
  • La soustraction - existe, et a été définie dans les méthodes.

Multiplication⚓︎

  • PAS de produit entre deux ensembles, ni entre un ensemble et un entier
  • PAS de division entre deux ensembles

Les ensembles sont mutables⚓︎

Par exemple, on peut modifier un ensemble set in situ, par exemple avec une méthode d'ajout d'élément .add(element), EN CONSERVANT LA MÊME ADRESSE MÉMOIRE (qui est en fait un pointeur vers le début de l'ensemble).

>>> s = set([1,2,3])
>>> id(s) # renvoie l'adresse mémoire du début de l'ensemble
# Exemple de réponse:
140205838266432
# ajout d'élément :
>>> s.add(4)
>>> id(s) # la 'nouvelle' adresse mémoire de l'ensemble est inchangée
          # (ici, ajout d'élément)
140205838266432

On s'aperçoit que les deux adresses mémoires, ou pointeurs, AVANT et APRÈS modification de l'ensemble, sont encores égales.

Mutabilité des Ensembles

Les ensembles sont mutables.

Remarque Le fait que les ensembles soient mutables laisse à penser qu'il s'agit d'un type (/structure) de données Python spécialement prévu pour être modifiable avec une bonne efficacité.

Hashabilité⚓︎

Pte

Les éléments d'un set doivent être hashables

# en particulier: PAS de liste à l'intérieur d'un ensemble
>>> s = {[1,2,3]}
TypeError: unhashable type: 'list'
# ni d'ensemble à l'interieur d'un ensemble
>>> s = { {1,2,3}}
TypeError: unhashable type: 'set'

Hashable

Un objet est dit hashable (cf Glossaire 🐍) s'il a une empreinte 🇫🇷 ou (valeur de) hash 🇬🇧 qui ne change jamais pendant sa durée de vie :

  • il doit donc implémenter une méthode __hash__() et
  • il doit être comparable à d'autres objets avec la méthode __eq__() (ou __cmp__() ) Deux objets hashables considérés égaux doivent avoir la même valeur de hash/empreinte.

Quels objets sont hashables ?

  • La plupart des objets natifs immutables de Python sont hashables :
    • Tous les types de base immutables (int, float, str) en Python sont hashables
    • Les conteneurs immutables (comme les tuples ou les frozensets), sont hashables lorsque leurs éléments sont hashables.
  • Les conteneurs mutables (comme les ensembles set, ou les listes list ou les dictionnaires dict), ne sont PAS hashables.

Utilisation des Objets Hashables

Un objet hashable peut être utilisé :

  • comme clé de dictionnaire
  • comme élément d'un ensemble

Remarque (Terminale NSI) : Les instances des classes définies par l'utilisateur sont hashables par défaut. Elles sont toutes différentes entre elles (non égales, au sens de __eq__()), et leur valeur de hash est déterminé par leur id().

Compréhensions d'Ensembles⚓︎

Une compréhension d'ensembles, ou ensemble en compréhension, est une syntaxe pour créer/générer un ensemble en une seule ligne de commande, en y incluant une boucle for sur une seule ligne.

Syntaxe sans if⚓︎

# 'iterable' est un itérable : une chaîne, une liste, un tuple, un ensemble, range(), etc...
>>> {fonction(item) for item in iterable}
>>> set(fonction(item) for item in iterable)

Remarque : les chaînes, les listes, les tuples, les ensembles, range(), etc... sont des itérables.


Exp

>>> {i for i in range(5)}
>>> {i for i in [0,1,2,3,4]}
>>> {i for i in (0,1,2,3,4)}
>>> set(i for i in (0,1,2,3,4))

# Renvoient tous l'ensemble
{0, 1, 2, 3, 4}

Syntaxe avec un if⚓︎

# 'iterable' est un itérable : une chaîne, une liste, un tuple, un ensemble, range(), etc...
>>> {fonction(item) for item in iterable if condition(item)}
>>> set(fonction(item) for item in iterable if condition(item))

Exp

>>> {i for i in range(21) if i%4!=0}
>>> set(i for i in range(21) if i%4!=0)

# Renvoient chacune :
{1, 2, 3, 5, 6, 7, 9}

REMARQUE / ATTENTION Le if DOIT être placé APRÈS le for, du moins lorsque le if est tout seul (c'est-à-dire non accompagné d'un else). En particulier, la syntaxe suivante, que l'on pourrait naïvement croire équivalente, NE FONCTIONNE PAS:

>>> {i if i%4!=0 for i in range(21)}
>>> set(i if i%4!=0 for i in range(21))
SyntaxError: invalid syntax

Syntaxe avec un if ET un else⚓︎

# 'iterable' est un itérable : une chaîne, une liste, un tuple, un ensemble, range(), etc...
>>> {fonction(item) if condition(item) else autreFonction(item) for item in iterable}
>>> set(fonction(item) if condition(item) else autreFonction(item) for item in iterable)

Exp

>>> {i if i%4!=0 else "bissextile" for i in range(21)}
>>> set(i if i%4!=0 else "bissextile" for i in range(21))
# Renvoient chacune :
{1, 2, 3, 5, 6, 7, 9, 'bissextile'}

REMARQUE / ATTENTION Le if DOIT être placé APRÈS le for, du moins lorsque le if est tout seul (c'est-à-dire non accompagné d'un else). En particulier, la syntaxe suivante, que l'on pourrait naïvement croire équivalente, NE FONCTIONNE PAS:

>>> {i for i in range(21) if i%4!=0 else "bissextile"}
>>> set(i for i in range(21) if i%4!=0 else "bissextile")
SyntaxError: invalid syntax