La notion de liste
Cours complet · algorithmique et programmation (première), chapitre 2 · première, algorithmique et programmation
Travailler ce chapitre sur Adloun
Voici la nouveauté de l'année : la liste, structure qui rassemble plusieurs valeurs en un seul objet. Elle est partout en mathématiques — termes d'une suite, tableau de valeurs d'une fonction, série statistique, échantillon simulé — et son écriture en compréhension est le décalque exact de la notation des ensembles. Conformément au programme, on se limite aux listes, sans présenter d'autres collections.
2.1 Qu'est-ce qu'une liste ?
Une liste est une suite finie et ordonnée de valeurs, écrites entre crochets et séparées par des virgules. Chaque élément est repéré par son indice, qui commence à :
L = [12, 8, 15, 10]
print(len(L)) # 4 : longueur de la liste
print(L[0]) # 12 : premier élément (indice 0 !)
print(L[3]) # 10 : dernier élément (indice len(L) - 1)
print(L[-1]) # 10 : indice négatif = on compte depuis la fin
Une liste est une rangée de cases numérotées à partir de : accéder à L[len(L)] provoque une IndexError.
Contrairement à un ensemble (chapitre 1 du manuel de mathématiques), une liste tient compte de l'ordre et des répétitions : [1, 2, 2] et [2, 1, 2] sont deux listes différentes, de longueur . C'est la bonne structure pour une suite de valeurs ; l'analogie avec les ensembles se joue ailleurs — dans la génération en compréhension.
2.2 Générer une liste : trois façons
2.2.1 En extension
On énumère tous les éléments, comme un ensemble écrit en extension :
diviseurs_de_12 = [1, 2, 3, 4, 6, 12]
notes = [12.5, 9, 14, 11]
vide = [] # la liste vide, prête à être remplie
2.2.2 Par ajouts successifs
On part de la liste vide et on ajoute au fil d'une boucle avec append — le mode de génération naturel pour les suites récurrentes, où chaque valeur dépend de la précédente :
L = []
u = 5
for i in range(10):
L.append(u) # ajoute u À LA FIN de L
u = 0.8 * u + 4
# L contient les 10 premiers termes de la suite u_{n+1} = 0.8 u_n + 4
2.2.3 En compréhension
On décrit les éléments par une formule et un domaine, exactement comme un ensemble écrit en compréhension :
[n**2 for n in range(10)]
carres = [n**2 for n in range(10)] # [0, 1, 4, 9, ..., 81]
pairs = [2*k for k in range(5)] # [0, 2, 4, 6, 8]
images = [x**2 - 3*x for x in range(-2, 4)] # tableau de valeurs de f
On peut filtrer le domaine par une condition, introduite par if :
multiples_de_3 = [n for n in range(30) if n % 3 == 0]
diviseurs = [d for d in range(1, 13) if 12 % d == 0] # [1, 2, 3, 4, 6, 12]
C'est le décalque de : la condition est une proposition au sens du chapitre de logique, que l'on peut combiner avec and, or, not.
# multiples de 3 OU de 5 (le « ou » inclusif de la logique)
L1 = [n for n in range(20) if n % 3 == 0 or n % 5 == 0]
# [0, 3, 5, 6, 9, 10, 12, 15, 18]
# pairs ET non multiples de 4
L2 = [n for n in range(20) if n % 2 == 0 and not n % 4 == 0]
# [2, 6, 10, 14, 18]
Écrire la condition d'une compréhension, c'est traduire une proposition mathématique — l'occasion de réviser négation, « et », « ou ».
Méthode : Choisir son mode de génération
- extension : peu d'éléments, connus explicitement (données d'un énoncé) ;
- ajouts successifs : les éléments se calculent de proche en proche (suites récurrentes, accumulations) ;
- compréhension : les éléments se calculent par une formule appliquée à un domaine, éventuellement filtré (suites explicites, tableaux de valeurs, sélections).
2.3 Manipuler une liste et ses indices
L = [5, 3, 8, 3]
L[1] = 7 # modification par indice : [5, 7, 8, 3]
L.append(10) # ajout en fin : [5, 7, 8, 3, 10]
L.insert(1, 99) # insertion à l'indice 1 : [5, 99, 7, 8, 3, 10]
L.remove(3) # supprime la PREMIÈRE occurrence de la valeur 3
x = L.pop() # retire ET renvoie le dernier élément
del L[0] # supprime l'élément d'indice 0
print(8 in L) # True : test d'appartenance (l'analogue du "appartient")
print(L.count(7)) # nombre d'occurrences de 7
print(len(L), min(L), max(L), sum(L)) # longueur, extrêmes, somme
L.remove(v) supprime par valeur (la première occurrence de v) ; del L[i] supprime par indice. Après toute suppression ou insertion, les indices des éléments suivants sont décalés — source classique d'erreurs quand on modifie une liste en la parcourant.
L[a:b] extrait la sous-liste des indices à (borne de fin exclue, comme range) :
L = [10, 11, 12, 13, 14, 15]
print(L[1:4]) # [11, 12, 13]
print(L[:3]) # [10, 11, 12] (début omis = depuis le début)
print(L[3:]) # [13, 14, 15] (fin omise = jusqu'au bout)
La borne de fin exclue se retient beaucoup mieux si l'on place les indices entre les cases plutôt que dessus :
2.4 Parcourir une liste
Méthode : Deux parcours équivalents
- Itérer sur les éléments :
for x in L:— on reçoit chaque valeur ; c'est le parcours à privilégier quand on n'a pas besoin des positions. - Itérer sur les indices :
for i in range(len(L)):— on accède àL[i], et l'on peut modifier la liste ou comparer des éléments voisins (L[i]etL[i+1]).
Les deux parcours ne donnent pas la même chose à la variable de boucle, et c'est tout ce qui les sépare :
def moyenne(L):
# AGRÉGER : accumuler une somme sur les éléments
total = 0
for x in L:
total += x
return total / len(L)
def maximum(L):
# SÉLECTIONNER : garder le meilleur élément rencontré
m = L[0]
for x in L:
if x > m:
m = x
return m
def compte_positifs(L):
# COMPTER : incrémenter sous condition
c = 0
for x in L:
if x > 0:
c += 1
return c
def positions_de(L, v):
# LOCALISER : parcours par indices pour renvoyer des positions
P = []
for i in range(len(L)):
if L[i] == v:
P.append(i)
return P
notes = [12, 8, 15, 10, 15]
print(moyenne(notes), maximum(notes)) # 12.0 15
print(compte_positifs([-2, 5, 0, 3])) # 2
print(positions_de(notes, 15)) # [2, 4]
(Les fonctions sum, max, min de Python font le même travail — mais savoir les réécrire est exigible, et c'est le prototype des fonctions statistiques du chapitre 4.)
def est_croissante(L):
# Parcours par indices : on compare chaque terme au suivant
for i in range(len(L) - 1):
if L[i] > L[i + 1]:
return False # un contre-exemple suffit !
return True
La logique du chapitre 1 du manuel affleure : pour réfuter « pour tout , », on exhibe un contre-exemple (return False immédiat) ; pour l'affirmer, il faut avoir tout vérifié (le return True n'arrive qu'après la boucle).
2.5 Listes et mathématiques : premiers ponts
def tableau_valeurs(f, a, b, n):
# n + 1 valeurs de f régulièrement espacées sur [a, b]
pas = (b - a) / n
X = [a + k * pas for k in range(n + 1)]
Y = [f(x) for x in X]
return X, Y
X, Y = tableau_valeurs(lambda x: x**2 - 2*x, 0, 3, 6)
# X = [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
# Y = [0.0, -0.75, -1.0, -0.75, 0.0, 1.25, 3.0]
Deux compréhensions suffisent : l'une pour les abscisses, l'autre pour les images. On lit sur Y le minimum atteint en — le sommet de la parabole. Les chapitres suivants systématisent ces allers-retours entre listes et notions mathématiques.
2.6 Exercices d'entraînement
Difficulté : ★ facile ★ moyen ★ plus difficile.
Génération
Exercice
Écrire en compréhension : (a) la liste des cubes de à ; (b) la liste des entiers de à multiples de ; (c) la liste des valeurs de pour de à .
Solution
(a) [n**3 for n in range(11)] (b) [n for n in range(1, 51) if n % 7 == 0] (c) [1/n for n in range(1, 11)].
Exercice
Traduire chaque ensemble en une liste Python en compréhension :
Solution
A = [2*k + 1 for k in range(10)] (les dix premiers impairs).
B = [n for n in range(100) if n % 4 == 0 and n % 6 != 0] — la condition combine un « et » et une négation, comme la proposition mathématique.
Exercice
Générer par ajouts successifs la liste des premiers termes de la suite de Fibonacci, puis en extraire (par compréhension) ceux qui sont pairs.
Solution
F = [1, 1]
for i in range(13):
F.append(F[-1] + F[-2])
pairs = [x for x in F if x % 2 == 0]
# F : [1, 1, 2, 3, 5, 8, ..., 610] ; pairs : [2, 8, 34, 144, 610]
(Un terme sur trois est pair — le repérer, c'est déjà conjecturer.)
Manipulation et indices
Exercice
On pose L = [4, 7, 1, 7, 9]. Donner, sans machine, le contenu de L après chacune des instructions successives : L.append(2) ; L.remove(7) ; del L[0] ; L[2] = 5. Que vaut alors L[-2] ?
Solution
[4, 7, 1, 7, 9, 2] → [4, 1, 7, 9, 2] (première occurrence de retirée) → [1, 7, 9, 2] → [1, 7, 5, 2]. Et L[-2] vaut 5.
Exercice
Écrire une fonction inverse(L) qui renvoie une nouvelle liste contenant les éléments de L en ordre inverse, sans utiliser reversed ni L[::-1].
Solution
def inverse(L):
R = []
for i in range(len(L) - 1, -1, -1): # indices de len-1 à 0
R.append(L[i])
return R
Variante par valeurs : for x in L: R.insert(0, x).
Parcours
Exercice
Écrire une fonction deuxieme_max(L) renvoyant le deuxième plus grand élément d'une liste d'au moins deux nombres distincts.
Solution
def deuxieme_max(L):
m1, m2 = max(L[0], L[1]), min(L[0], L[1])
for x in L[2:]:
if x > m1:
m2, m1 = m1, x # x devient le max, l'ancien max recule
elif x > m2:
m2 = x
return m2
Un seul parcours suffit : on entretient les deux meilleurs rencontrés.
Exercice
Écrire une fonction ecart_maximal(L) renvoyant le plus grand écart entre deux éléments consécutifs. L'appliquer de tête à [3, 8, 5, 11, 10].
Solution
def ecart_maximal(L):
m = 0
for i in range(len(L) - 1):
ecart = abs(L[i + 1] - L[i])
if ecart > m:
m = ecart
return m
Écarts : — le maximum est .
Exercice
Écrire une fonction diviseurs(n) (liste en compréhension), puis est_parfait(n) qui teste si est égal à la somme de ses diviseurs stricts (hors ). Vérifier que et sont parfaits, puis chercher le suivant par une boucle.
Solution
def diviseurs(n):
return [d for d in range(1, n + 1) if n % d == 0]
def est_parfait(n):
return sum(diviseurs(n)) - n == n # somme des diviseurs stricts
parfaits = [n for n in range(2, 10000) if est_parfait(n)]
# [6, 28, 496, 8128]
✓ et ✓ ; le suivant est . Trois fonctions en cascade : la modularité du chapitre 5 pointe déjà.
Exercice
Que renvoie mystere([12, 8, 15, 10, 15]) ? Décrire en une phrase ce que fait cette fonction.
def mystere(L):
R = []
for x in L:
if x not in R:
R.append(x)
return R
Solution
Elle renvoie [12, 8, 15, 10] : la liste sans doublons, dans l'ordre de première apparition — c'est le passage de la liste (avec répétitions) à l'ensemble de ses valeurs, en conservant un ordre.