UKOnline

Ensemble

Un ensemble est une collection d'éléments distincts. Contrairement aux séquences que l'on a vues précédemment, les éléments d'un ensemble ne sont pas ordonnés et il ne contient pas d'éléments dupliqués.

Définition d'un ensemble

Python propose le type de données set pour représenter les ensembles. On peut créer un ensemble en listant tous ses éléments, séparés par des virgules, et en délimitant le tout avec des accolades. L'exemple suivant crée un ensemble avec cinq nombres entiers :

Plusieurs opérations et fonctions applicables aux séquences le sont également aux ensembles. On peut notamment connaitre la taille d'un ensemble avec la fonction len et tester si un élément en fait partie ou non avec l'opérateur in. On ne peut par contre pas accéder aux éléments d'un ensemble à partir d'un indice, comme avec les séquences, puisqu'il s'agit d'une collection non ordonnée d'éléments. On ne peut donc parcourir les éléments d'un ensemble qu'avec la boucle for. L'exemple suivant illustre ces différentes opérations :

Comme on peut le constater sur le résultat de l'exécution de ces instructions, le type de la variable numbers est bien set :

{0, 42, 11, -2, 7}
5
False
<class 'set'>

0
42
11
-2
7

Notez que l'affichage des éléments de l'ensemble peut tout à fait changer d'une exécution à l'autre. En effet, puisque les éléments d'un ensemble ne sont pas ordonnés, il n'y a pas d'unique ordre de parcours. La boucle for garantit juste que chaque élément de l'ensemble sera visité une fois.

Il y a deux autres manières courantes de créer un ensemble. Tout d'abord, on peut en créer un nouveau par compréhension, tout comme on l'a déjà fait avec les séquences. Par exemple, pour définir l'ensemble des entiers inférieurs à $100$ qui sont à la fois divisibles par $3$ et $7$, on écrit :

Cette notation est très similaire à celle utilisée en mathématiques pour définir des ensembles où on aurait écrit :

$$S = \Big\{ n \in \mathbb{N} \textrm{ avec } 0 \leq n < 100 \quad\Big|\quad n \textrm{ est divisible par } 3 \textrm{ et } 7 \Big\}.$$

Ensuite, on peut également créer un ensemble à partir d'une séquence. On utilise pour cela la fonction set en lui passant comme paramètre la séquence sur base de laquelle il faut créer l'ensemble. Voici deux exemples créant un ensemble, à partir d'une liste et d'une chaine de caractères :

Puisqu'un ensemble ne peut contenir de doublons, ils seront automatiquement supprimés comme on le voit sur le résultat de l'exécution :

{0, 42, 12, -1}
{'r', '!', 'o', 'c', 'C', 'i'}

Si on veut définir un ensemble vide, il faut utiliser la fonction set :

En effet, comme on le verra à la section suivante, la notation {} permet de définir un dictionnaire vide.

Modification d'un ensemble

Une fois un ensemble créé, il est possible d'en obtenir des informations ou de le modifier en utilisant des fonctions dédiées aux ensembles. On a déjà vu les fonctions len et in, dans la section précédente, qui permettent respectivement de connaitre la taille de l'ensemble et de tester l'appartenance d'un élément à un ensemble.

Une fois un ensemble créé, on peut le modifier, c'est-à-dire lui ajouter ou retirer des valeurs. On utilise pour cela les fonctions add et remove, qu'il faut appliquer sur la variable contenant l'ensemble. L'exemple suivant crée un ensemble, lui retire deux valeurs et en ajoute une :

Comme on peut le constater sur le résultat de l'exécution de ces instructions, l'ensemble qui contenait cinq éléments au départ ne contient plus que les trois éléments $3$, $4$ et $5$ :

{3, 4, 5}

Si on tente de supprimer un élément qui ne fait pas partie de l'ensemble, la fonction remove provoquera une erreur lors de l'exécution. Il existe également une fonction discard qui, elle, ne produira pas d'erreur. Enfin, il existe également une fonction pop qui permet d'obtenir l'un des éléments de l'ensemble, le retirant de ce dernier.

Opération ensembliste

Comme on le verra en détails plus loin, Python est un langage de programmation très riche qui permet notamment de définir des opérations spécifiques à chaque nouveau type de données.

En l'occurrence, il est possible d'effectuer des opérations ensemblistes sur les données de type set. La figure 1 illustre le résultat de ces différentes opérations, étant donné deux ensembles $A$ et $B$ :

  • la différence entre $A$ et $B$, notée $A \setminus B$, contient tous les éléments de $A$ sauf ceux appartenant également à $B$ ;
  • l'union des ensembles $A$ et $B$, notée $A \cup B$, contient tous les éléments se trouvant soit dans $A$, soit dans $B$ (ou dans les deux) ;
  • l'intersection des ensembles $A$ et $B$, notée $A \cap B$, contient tous les éléments se trouvant à la fois dans $A$ et dans $B$.
Opération ensembliste
Les trois principales opérations ensemblistes sont la différence, l'union et l'intersection d'ensembles.

Comme on le verra plus loin dans ce livre, le langage Python permet de redéfinir des opérateurs pour tout nouveau type de données. En l'occurrence, les opérations ensemblistes de différence, d'union et d'intersection sont respectivement calculées par les opérateurs -, | et &. Voici un exemple utilisant ces trois opérateurs :

Voici le diagramme de Venn reprenant les deux ensembles $A$ et $B$, ainsi que leurs différents éléments :

Exemple d'opérations ensemblistes

Le résultat de l'exécution de ces instructions correspond bien aux résultats attendus des opérations ensemblistes, et est conforme à ce que l'on peut observer sur le diagramme de Venn :

{1, 2}
{3, 4}
{1, 2, 3, 4, 5}

Tout comme c'est le cas avec les opérateurs arithmétiques, on peut également construire des affectations composées à partir des opérateurs ensemblistes. Cela nous offre donc une autre possibilité pour modifier un ensemble. Étant donné un ensemble A, les deux séquences d'instructions suivantes sont (presque) équivalentes :

Pourquoi ces opérations ne sont-elles pas complètement équivalentes ? Pour vraiment le comprendre, il faudra attendre le prochain chapitre sur les objets, mais on peut néanmoins déjà comprendre l'intuition grâce à la figure 2 qui montre la situation en mémoire.

Sur la gauche, on peut voir la situation en mémoire avant et après exécution de l'instruction A.add(6). L'ensemble contenu dans la variable A a été modifié et contient un élément supplémentaire.

Sur la droite, on peut voir la situation en mémoire avant et après exécution de l'instruction A |= {6}. La valeur de la variable A a été modifiée et contient un nouvel ensemble, résultat de l'union de l'ensemble A original avec l'ensemble {6}. L'ensemble qui était originellement référencé par la variable A reste en mémoire, mais n'est plus accessible.

Comparaison add et union
Ajouter un élément à un ensemble en passant par la fonction add n'est pas équivalent à utiliser une affectation composée avec l'opérateur d'union, même si le résultat observé est le même.

Ensemble non modifiable

Terminons cette section en nous penchant sur l'aspect modifiable des éléments d'un ensemble, mais également de l'ensemble en tant que tel.

La première contrainte importante concernant les éléments d'un ensemble est qu'ils doivent être non modifiables. Voyons cela avec l'exemple suivant qui tente de créer un ensemble contenant des listes :

Les listes étant modifiables, on ne peut en utiliser comme éléments d'un ensemble et une erreur d'exécution est produite par l'interpréteur :

Traceback (most recent call last):
  File "program.py", line 1, in <module>
    A = {[0], [1, 2, 3]}
TypeError: unhashable type: 'list'

Et qu'en est-il des ensembles ? Comme on l'a vu précédemment, un ensemble est modifiable puisqu'on peut lui ajouter ou retirer des éléments une fois créé, notamment avec add et remove.

Il est possible de définir un ensemble non modifiable, c'est-à-dire que l'on ne pourra pas le modifier une fois créé. Pour cela, on utilise simplement le type frozenset au lieu du type set. Tout ce que l'on a vu précédemment pour les ensembles, à l'exception des opérations de modification, est également applicable aux frozenset.

L'exemple suivant crée un ensemble non modifiable et tente de lui ajouter un élément :

Puisque la variable A contient un ensemble non modifiable, une erreur se produit lors de l'exécution :

Traceback (most recent call last):
  File "program.py", line 2, in <module>
    A.add(6)
AttributeError: 'frozenset' object has no attribute 'add'

Néanmoins, notez que l'instruction A |= {6} s'exécute sans produire d'erreur. Après son exécution, la variable A contiendra un nouvel ensemble, à savoir frozenset({1, 2, 3, 6}). On n'a donc pas modifié l'ensemble originellement présent dans A, mais on lui a affecté un nouvel ensemble non modifiable.