Ensembles, relations et fonctions¶
Modéliser un domaine métier commence par définir des ensembles et des relations — avant d'écrire une ligne de code.
Pourquoi les ensembles avant le code¶
Un système de gestion de commandes manipule des utilisateurs, des produits, des commandes, des états. Avant de choisir un ORM ou un schéma de base de données, la question fondamentale est : quelles entités existent, quelles relations les lient, quelles contraintes ces relations doivent respecter ?
La théorie des ensembles donne un vocabulaire précis pour répondre a ces questions. Un type de données est un ensemble. Une relation entre tables est une relation mathématique. Une fonction de transformation est une fonction mathématique. Cette équivalence n'est pas metaphorique — elle est opérationnelle : les propriétés mathématiques se traduisent directement en contraintes implementables et verifiables.
Ensembles et opérations fondamentales¶
Définitions de base¶
Un ensemble est une collection non ordonnée d'éléments distincts. Notations :
- En extension : \(A = \{1, 2, 3, 4\}\)
- En comprehension : \(A = \{x \in \mathbb{N} \mid x \leq 4\}\)
- Ensemble vide : \(\emptyset = \{\}\)
- Appartenance : \(x \in A\), \(x \notin A\)
- Inclusion : \(A \subseteq B\) (tout élément de A est dans B), \(A \subset B\) (inclusion stricte)
- Cardinalite : \(|A|\) = nombre d'éléments
En informatique : les types primitifs sont des ensembles (bool = {true, false}, uint8 = {0, ..., 255}). Les enumerations sont des ensembles finis. Les types somme (union discriminee) et produit (structures) ont une interpretation ensembliste directe.
Opérations binaires¶
Soient \(A\) et \(B\) deux ensembles inclus dans un univers \(U\).
Union : \(A \cup B = \{x \mid x \in A \text{ ou } x \in B\}\) Exemple : SELECT * FROM a UNION SELECT * FROM b
Intersection : \(A \cap B = \{x \mid x \in A \text{ et } x \in B\}\) Exemple : utilisateurs actifs ET ayant passe une commande ce mois
Différence : \(A \setminus B = \{x \mid x \in A \text{ et } x \notin B\}\) Exemple : SELECT * FROM a LEFT JOIN b ON ... WHERE b.id IS NULL
Complement : \(A^c = U \setminus A = \{x \in U \mid x \notin A\}\)
Différence symetrique : \(A \triangle B = (A \setminus B) \cup (B \setminus A)\) Exemple : éléments present dans l'un mais pas l'autre — utile pour la synchronisation, la détection de drift
| Opération | SQL | Python (set) |
|---|---|---|
| Union | UNION | a \| b |
| Intersection | INNER JOIN / INTERSECT | a & b |
| Différence | LEFT JOIN ... IS NULL | a - b |
| Différence symetrique | (non standard) | a ^ b |
Produit cartesien¶
Si \(|A| = m\) et \(|B| = n\), alors \(|A \times B| = m \times n\).
Le produit cartesien est le fondement du modèle relationnel. Une table SQL avec des colonnes de types \(T_1, T_2, \ldots, T_n\) est un sous-ensemble de \(T_1 \times T_2 \times \ldots \times T_n\). Une jointure sans condition de join produit le produit cartesien — d'ou son coût explosif quand les tables sont grandes.
Ensemble des parties¶
L'ensemble des parties de \(A\), note \(\mathcal{P}(A)\) ou \(2^A\), est l'ensemble de tous les sous-ensembles de \(A\).
Si \(A = \{1, 2, 3\}\) alors \(\mathcal{P}(A) = \{\emptyset, \{1\}, \{2\}, \{3\}, \{1,2\}, \{1,3\}, \{2,3\}, \{1,2,3\}\}\)
Si \(|A| = n\), alors \(|\mathcal{P}(A)| = 2^n\).
Application : les permissions d'un système peuvent être modelisees comme des sous-ensembles d'un ensemble de capacités. Avec 30 permissions possibles, il y a \(2^{30} \approx 10^9\) combinaisons — trop nombreuses pour être listees, mais structurées par les opérations ensemblistes.
Note
Les lois de De Morgan s'appliquent aux opérations ensemblistes exactement comme aux connecteurs logiques : \((A \cup B)^c = A^c \cap B^c\) et \((A \cap B)^c = A^c \cup B^c\). Les requêtes SQL avec NOT IN et NOT EXISTS obéissent a ces lois — les comprendre evite les bugs de logique de requête.
Relations¶
Définition¶
Une relation \(R\) de \(A\) vers \(B\) est un sous-ensemble du produit cartesien \(A \times B\). On écrit \(xRy\) ou \((x, y) \in R\) pour indiquer que \(x\) est en relation avec \(y\).
Une relation de \(A\) vers \(A\) est une relation sur \(A\) (relation homogène). La plupart des propriétés structurelles intéressantes s'appliquent aux relations homogènes.
Propriétés des relations¶
Soit \(R\) une relation sur un ensemble \(A\).
| Propriété | Définition formelle | Exemple |
|---|---|---|
| Reflexive | ∀x ∈ A, xRx | "est le même âge que" |
| Symetrique | ∀x,y ∈ A, xRy → yRx | "est ami avec" |
| Antisymetrique | ∀x,y ∈ A, xRy ∧ yRx → x=y | "est inférieur ou egal a" |
| Transitive | ∀x,y,z ∈ A, xRy ∧ yRz → xRz | "est ancêtre de" |
| Totale | ∀x,y ∈ A, xRy ∨ yRx | "est comparable a" |
Ordres¶
Un ordre partiel est une relation reflexive, antisymetrique et transitive. Notation : \(\leq\) ou \(\subseteq\).
Un ordre total est un ordre partiel ou de plus toute paire d'éléments est comparable (relation totale). Tout entier est comparable à tout autre entier — la relation \(\leq\) sur \(\mathbb{Z}\) est un ordre total. En revanche, la relation de divisibilite sur \(\mathbb{N}\) est un ordre partiel : 3 et 5 ne sont ni l'un diviseur de l'autre.
Application système : les versions de dépendances (semver) forment un ordre partiel. Un ensemble de contraintes de versions peut être insatisfaisable si les intervalles ne se chevauchent pas — c'est un problème de satisfiabilite d'un système de contraintes sur un ordre partiel.
Treillis : un ordre partiel ou toute paire d'éléments admet une borne supérieure minimale (supremum) et une borne inférieure maximale (infimum). Les treillis apparaissent dans l'analyse de types, les lattices de sécurité (Bell-LaPadula), et les CRDT (structures de données replicables sans coordination).
graph TD
ABC["{a,b,c}"]
AB["{a,b}"]
AC["{a,c}"]
BC["{b,c}"]
A["{a}"]
B["{b}"]
C["{c}"]
E["∅"]
ABC --> AB
ABC --> AC
ABC --> BC
AB --> A
AB --> B
AC --> A
AC --> C
BC --> B
BC --> C
A --> E
B --> E
C --> E Treillis de l'ensemble des parties de {a,b,c} ordonné par inclusion.
Relations d'équivalence et classes d'équivalence¶
Une relation d'équivalence est reflexive, symetrique et transitive. Elle partitionne l'ensemble en classes d'équivalence — des sous-ensembles disjoints dont l'union est l'ensemble complet.
Notation : \(x \sim y\) (\(x\) et \(y\) sont équivalents). La classe d'équivalence de \(x\) est \([x] = \{y \mid y \sim x\}\).
Exemples applicatifs :
- Egalite modulo \(n\) : les entiers modulo 7 forment 7 classes d'équivalence. La cryptographie RSA repose sur l'arithmetique modulaire.
- Canonicalisation d'URLs : toutes les URLs qui résolvent vers la même ressource sont équivalentes pour la mise en cache.
- Deduplication de logs : deux messages de log avec le même template mais des parametres différents appartiennent à la même classe — utile pour l'agrégation.
Tip
En base de données, un index d'egalite regroupe les enregistrements par classe d'équivalence. Un index de B-tree exploite l'ordre partiel. Comprendre quelle structure mathématique sous-tend une donnée guide le choix de la structure d'index.
Fonctions¶
Définition et types¶
Une fonction \(f : A \to B\) associe à chaque élément de \(A\) (le domaine) exactement un élément de \(B\) (le codomaine). L'ensemble des valeurs effectivement prises par \(f\) est l'image \(\text{Im}(f) \subseteq B\).
| Type | Propriété | Définition | Exemple |
|---|---|---|---|
| Injective | "One-to-one" | ∀x,y, f(x)=f(y) → x=y | f(x) = 2x sur ℤ |
| Surjective | "Onto" | ∀y ∈ B, ∃x ∈ A, f(x)=y | f(x) = x mod 2 sur ℕ → |
| Bijective | Les deux | Injective et surjective | f(x) = x+1 sur ℤ |
Une fonction injective ne "fusionne" pas deux entrees sur la même sortie — elle préservé la distinction. Une fonction surjective couvre tout le codomaine — chaque valeur de sortie possible est atteinte.
Composition de fonctions¶
Si \(f : A \to B\) et \(g : B \to C\), alors la composition \(g \circ f : A \to C\) est définie par \((g \circ f)(x) = g(f(x))\).
La composition est associative : \(h \circ (g \circ f) = (h \circ g) \circ f\).
from functools import reduce
from typing import Callable, TypeVar
T = TypeVar('T')
def compose(*fns: Callable) -> Callable:
"""Compose des fonctions de droite a gauche."""
return reduce(lambda f, g: lambda x: f(g(x)), fns)
# Exemple : pipeline de transformation de donnee
normaliser = lambda s: s.strip().lower()
supprimer_accents = lambda s: s.encode('ascii', 'ignore').decode()
truncate = lambda s: s[:50]
# La composition est une nouvelle fonction
nettoyer = compose(truncate, supprimer_accents, normaliser)
resultat = nettoyer(" Entrée COMPLEXE avec Accents ")
# Les middlewares HTTP sont des compositions de fonctions
# handler : Request -> Response
# middleware : (Request -> Response) -> (Request -> Response)
def auth_middleware(handler):
def wrapped(request):
if not request.headers.get('Authorization'):
return Response(401, 'Unauthorized')
return handler(request)
return wrapped
def logging_middleware(handler):
def wrapped(request):
print(f"→ {request.method} {request.path}")
response = handler(request)
print(f"← {response.status}")
return response
return wrapped
# app = logging_middleware(auth_middleware(core_handler))
# Correspond mathematiquement a : logging ∘ auth ∘ core
Fonction inverse¶
Une fonction bijective \(f : A \to B\) admet une inverse \(f^{-1} : B \to A\) telle que \(f^{-1} \circ f = \text{id}_A\) et \(f \circ f^{-1} = \text{id}_B\).
Seules les bijections sont inversibles. Une fonction injective non surjective peut être inversee sur son image mais pas sur tout \(B\). Une fonction non injective (plusieurs entrees vers la même sortie) ne peut pas être inversee — l'information a été perdue.
Application : les fonctions de hachage ne sont pas bijectives (plusieurs entrees peuvent donner le même hash, et la sortie est plus petite que le domaine) — elles sont donc fondamentalement non inversibles, ce qui fonde leur utilisation en cryptographie.
Applications : systèmes de types et modélisation de données¶
Le système de types comme théorie des ensembles¶
Chaque type est un ensemble. Chaque valeur est un élément. Les constructions du langage correspondent à des opérations ensemblistes :
| Construction | Interpretation ensembliste | Exemple |
|---|---|---|
| Type primitif | Ensemble fini | bool = {True, False} |
| Type somme (union) | Union disjointe | Optional[T] = T ∪ {None} |
| Type produit (tuple) | Produit cartesien | tuple[A, B] ⊆ A × B |
| Fonction | Relation fonctionnelle | f: A → B |
| Générique | Famille de fonctions | List[T] pour chaque T |
| Sous-type | Inclusion d'ensembles | int ⊆ float |
from typing import Union, Literal
# Type somme : un seul element d'une union
Statut = Literal['actif', 'inactif', 'suspendu']
# Statut = {'actif', 'inactif', 'suspendu'} — ensemble fini
# Type produit : combinaison de types
from dataclasses import dataclass
@dataclass
class Utilisateur:
id: int # element de ℕ
email: str # element de Σ* (mots sur un alphabet)
statut: Statut # element de {'actif', 'inactif', 'suspendu'}
# Utilisateur ⊆ ℕ × Σ* × Statut
Modélisation de données : relations et cardinalites¶
Les cardinalites des relations de base de données sont des propriétés de fonctions :
| Cardinalite | Signification mathématique | Exemple |
|---|---|---|
| 1:1 | Fonction bijective | Utilisateur ↔ Profil |
| 1:N | Fonction non injective | Département → Employes |
| N:1 | Fonction quelconque | Commande → Client |
| N:M | Relation quelconque | Produits ↔ Commandes |
Une relation N:M ne peut pas être représentée par une simple fonction — elle nécessité une table d'association (table de jointure) qui materialise la relation comme un sous-ensemble du produit cartesien.
Partitionnement et sharding¶
Le sharding d'une base de données est une partition de l'ensemble des données. Une partition de \(A\) est un ensemble de sous-ensembles disjoints de \(A\) dont l'union est \(A\) — exactement la structure des classes d'équivalence d'une relation d'équivalence.
def shard_key(user_id: int, nb_shards: int) -> int:
"""Partitionne les utilisateurs en nb_shards groupes disjoints."""
return user_id % nb_shards
# Relation d'equivalence : user_a ~ user_b si user_a % nb_shards == user_b % nb_shards
# Produit nb_shards classes d'equivalence disjointes
Pour que le sharding soit équilibre, la fonction de shard doit être "presque surjective" — chaque shard reçoit approximativement le même nombre de clés. Le hachage cohérent (consistent hashing) optimise cette propriété en minimisant la redistribution lors de l'ajout ou de la suppression de nœuds.
Clôture et fermeture transitive¶
La fermeture transitive d'une relation \(R\) est la plus petite relation transitive contenant \(R\). Notation : \(R^+\).
En pratique : si on a une relation "dépend de" entre modules, la fermeture transitive donne toutes les dépendances transitives. Calculer la fermeture transitive d'un graphe de dépendances détecté les dépendances circulaires (cycles dans le graphe).
-- Fermeture transitive en SQL (CTE recursive)
WITH RECURSIVE dependances_transitives AS (
-- Cas de base : dependances directes
SELECT source, cible FROM dependances
UNION ALL
-- Etape : etendre par transitivite
SELECT dt.source, d.cible
FROM dependances_transitives dt
JOIN dependances d ON dt.cible = d.source
)
SELECT DISTINCT source, cible FROM dependances_transitives;
Warning
La fermeture transitive peut être très coûteuse si la relation contient des cycles — la requête recursive boucle indéfiniment. Toujours ajouter une condition de profondeur maximale ou utiliser un algorithme de détection de cycles avant de calculer la fermeture transitive.
Formulaire de référence¶
| Concept | Définition | Cas d'usage |
|---|---|---|
| Ensemble | Collection d'éléments distincts | Types, domaines de valeurs |
| Relation | Sous-ensemble de A × B | Tables de base de données, graphes |
| Ordre partiel | Reflexif, antisymetrique, transitif | Versions, dépendances, timestamps |
| Équivalence | Reflexif, symetrique, transitif | Partitionnement, deduplication |
| Injection | Pas de fusion de valeurs | Clés etrangeres, identifiants |
| Surjection | Toute sortie est atteignable | Fonctions de projection, hachage |
| Bijection | Inversible | Encodages, serialisation réversible |
| Fermeture transitive | Toutes les chaînes de relations | Dépendances, droits hérités |