1Les tuples : sequences immuables
Un tuple (ou \(n\)-uplet) est une sequence ordonnee et immuable. Il se note entre parentheses, mais ce sont en realite les virgules qui le creent.
t = (1, 2, 3)
t = 1, 2, 3 # les parentheses sont facultatives
t = () # tuple vide
t = (5,) # tuple a UN element : la virgule est obligatoire
t = (5) # ce n'est PAS un tuple, c'est l'entier 5
len((5,)), len((5)) # TypeError sur le second : un int n'a pas de longueur
t = (10, 20, 30)
t[0] # 10 indexation comme une liste
t[1:] # (20, 30) tranche
len(t) # 3
20 in t # True
t[0] = 99 # TypeError : les tuples sont immuables
t = t + (40,) # on construit un NOUVEAU tuple
Pourquoi des tuples ?
| Raison | Explication |
|---|---|
| Hachabilite | un tuple d'objets immuables peut servir de cle de dictionnaire ou d'element d'ensemble ; une liste ne le peut pas |
| Securite | l'immuabilite garantit qu'aucune fonction ne modifiera la donnee par effet de bord |
| Semantique | on l'utilise pour un enregistrement heterogene
((nom, age, ville)) plutot que pour une collection homogene |
| Retours multiples | return a, b construit un tuple
implicite |
grille = {}
grille[(0, 0)] = "depart" # cle-tuple : coordonnees
grille[(3, 4)] = "arrivee"
grille[[0, 0]] # TypeError: unhashable type: 'list'
Deballage (unpacking)
a, b = (1, 2) # deballage
a, b = b, a # echange (le membre droit est un tuple)
q, r = divmod(17, 5) # une fonction renvoyant deux valeurs
x, *reste = (1, 2, 3, 4) # x = 1, reste = [2, 3, 4]
*debut, z = (1, 2, 3) # debut = [1, 2], z = 3
(a, b), c = (1, 2), 3 # deballage imbrique
for i, (nom, note) in enumerate([("Sara", 18), ("Amine", 15)]):
print(i, nom, note)
t = (1, [2, 3])
t[1].append(4) # AUTORISE : on modifie la LISTE, pas le tuple
print(t) # (1, [2, 3, 4])
hash(t) # TypeError : le tuple n'est pas hachable
Un tuple garantit que ses references ne changent pas. Il n'est hachable que si tous ses elements le sont.
2Les dictionnaires : associer une valeur a une cle
Un dictionnaire associe a des cles (hachables, donc immuables et distinctes) des valeurs quelconques. C'est une table de hachage : l'acces, l'ajout et la suppression coutent \(\Theta(1)\) en moyenne, quelle que soit la taille.
d = {} # vide
d = {"a": 1, "b": 2} # en extension
d = dict(a=1, b=2) # par mots-cles
d = dict([("a", 1), ("b", 2)]) # depuis une liste de couples
d = {c: ord(c) for c in "abc"} # en comprehension
Operations
d = {"a": 1, "b": 2}
d["a"] # 1
d["z"] # KeyError !
d.get("z") # None : pas d'exception
d.get("z", 0) # 0 : valeur par defaut
d["c"] = 3 # ajout
d["a"] = 10 # modification
del d["b"] # suppression (KeyError si absente)
d.pop("b", None) # suppression sans risque
"a" in d # True : le test porte sur les CLES
len(d) # nombre de couples
Parcours
d = {"a": 1, "b": 2, "c": 3}
for cle in d: # par defaut : les CLES
print(cle, d[cle])
for cle in d.keys(): # explicite
...
for valeur in d.values():
...
for cle, valeur in d.items(): # la forme la plus utile
print(f"{cle} -> {valeur}")
sorted(d) # ['a', 'b', 'c'] : les cles triees
sorted(d.items(), key=lambda c: -c[1]) # par valeur decroissante
Depuis Python 3.7, un dictionnaire conserve l'ordre d'insertion des cles. C'est une garantie du langage, mais il ne faut pas confondre cela avec un ordre trie.
Motifs indispensables
def frequences(sequence):
"""Dictionnaire element -> nombre d'occurrences. Theta(n)."""
f = {}
for x in sequence:
f[x] = f.get(x, 0) + 1 # get evite le KeyError au premier passage
return f
assert frequences("abracadabra")["a"] == 5def regroupe(couples):
"""[(cle, val), ...] -> {cle: [val, ...]}"""
d = {}
for cle, val in couples:
d.setdefault(cle, []).append(val)
return d
assert regroupe([("a", 1), ("b", 2), ("a", 3)]) == {"a": [1, 3], "b": [2]}
d.setdefault(k, v) renvoie d[k] si la cle existe, sinon l'insere avec
la valeur v et la renvoie. C'est l'idiome standard pour construire un dictionnaire de
listes.
cache = {}
def fib(n):
"""Fibonacci memoise : Theta(n) au lieu de Theta(phi^n)."""
if n < 2:
return n
if n not in cache:
cache[n] = fib(n-1) + fib(n-2)
return cache[n]
assert fib(100) == 354224848179261915075Inversion et indexation
d = {"a": 1, "b": 2}
inverse = {v: k for k, v in d.items()} # {1: 'a', 2: 'b'}
L'inversion n'est correcte que si les valeurs sont deux a deux distinctes ; sinon des cles sont ecrasees silencieusement. Pour le cas general, il faut inverser vers un dictionnaire de listes.
3Les ensembles et synthese des structures
Un ensemble (set) est une collection non ordonnee,
sans doublon, d'objets hachables. C'est un dictionnaire sans les valeurs : le
test d'appartenance coute \(\Theta(1)\) en moyenne.
e = set() # ensemble VIDE ({} donnerait un dictionnaire !)
e = {1, 2, 3}
e = set([1, 2, 2, 3]) # {1, 2, 3} : les doublons disparaissent
e = {c for c in "abracadabra"} # {'a', 'b', 'r', 'c', 'd'}
e.add(4) # ajout
e.discard(9) # suppression sans erreur si absent
e.remove(9) # KeyError si absent
3 in e # Theta(1)
len(e)
Operations ensemblistes
| Notation | Methode | Sens |
|---|---|---|
A | B | union | \(A \cup B\) |
A & B | intersection | \(A \cap B\) |
A - B | difference | \(A \setminus B\) |
A ^ B | symmetric_difference | \(A \Delta B\) |
A <= B | issubset | \(A \subset B\) |
A >= B | issuperset | \(A \supset B\) |
A = {1, 2, 3, 4}
B = {3, 4, 5}
A | B # {1, 2, 3, 4, 5}
A & B # {3, 4}
A - B # {1, 2}
A ^ B # {1, 2, 5}
{1, 2} <= A # True
Ces operations coutent \(\Theta(|A| + |B|)\) : elles sont bien plus rapides que les boucles imbriquees equivalentes, en \(\Theta(|A|\cdot|B|)\).
Tableau de synthese
list | tuple |
dict | set | |
|---|---|---|---|---|
| Ordonne | oui | oui | ordre d'insertion | non |
| Mutable | oui | non | oui | oui |
| Doublons | oui | oui | cles distinctes | non |
| Hachable | non | oui* | non | non** |
| Acces | indice | indice | cle | appartenance |
Cout de in | \(\Theta(n)\) | \(\Theta(n)\) | \(\Theta(1)\) | \(\Theta(1)\) |
* si tous ses elements le sont —
** un frozenset, lui, est hachable.
L'essentiel du chapitre
- Un tuple a un element s'ecrit
(x,): c'est la virgule qui compte. - Seuls les objets hachables (donc immuables) peuvent etre cles ou elements d'ensemble.
d.get(k, defaut)evite lesKeyError;d.setdefault(k, [])construit un dictionnaire de listes.set()cree un ensemble vide ;{}cree un dictionnaire vide.- Les operations
|,&,-,^remplacent des boucles imbriquees et font gagner un ordre de grandeur. - Comptage, regroupement, memoisation : les trois motifs a maitriser.