Listes, Boucles & Premiers Algorithmes

Cours 2 — On arrête de faire simple, on fait des trucs utiles
🐍 Listes · Boucles · Polynômes · Vecteurs · Signaux

§1 Rappel éclair du cours 1

Dans le cours précédent, vous avez appris les variables, les conditions, les fonctions (récursives, et même d'ordre supérieur). Si ce n'est pas encore frais, un petit rappel :

def mention(note):
    if note >= 10:
        return "Validé"
    else:
        return "Non"

print(mention(14))   # Validé

Aujourd'hui, on ajoute deux briques fondamentales : les listes (pour stocker plusieurs valeurs) et les boucles (pour répéter des actions). Avec ça, vous pourrez manipuler des signaux, des polynômes, des vecteurs — bref, faire de vrais calculs.

Le cours 1, c'était le permis piéton. Le cours 2, c'est le code de la route. Après, vous pourrez conduire une Ferrari algorithmique. (Ou une 2CV, selon votre niveau de caféine.)

§2 Les listes : des boîtes à plusieurs compartiments

Une liste (ou array à une dimension), c'est une séquence ordonnée d'éléments. Imaginez un tiroir à compartiments : chaque compartiment contient une valeur, et chaque compartiment a un numéro (son indice).

# Une liste se crée avec des crochets
notes = [12, 15, 8, 19, 10]

# On accède à un élément par son indice (qui commence à 0)
print(notes[0])   # 12 — premier élément
print(notes[2])   # 8  — troisième élément

# Indices négatifs : on compte depuis la fin
print(notes[-1])  # 10 — dernier élément
print(notes[-2])  # 19 — avant-dernier

# Nombre d'éléments
print(len(notes))  # 5
12
8
10
19
5
Les indices qui commencent à 0 et pas à 1, c'est un sujet de débat ancestral. Les programmeurs comptent à partir de 0. Pourquoi ? Parce que l'indice est en fait un décalage par rapport au début de la liste. Le premier élément est à 0 décalage, le second à 1 décalage... C'est comme ça. Acceptez-le. Vous finirez par compter en base 0 dans la vie de tous les jours.

2.1 Modifier une liste

notes = [12, 15, 8, 19, 10]

notes[2] = 14           # remplacer le 8 par un 14
notes.append(16)    # ajouter 16 à la fin
notes.append(7)     # ajouter 7 à la fin

print(notes)
# [12, 15, 14, 19, 10, 16, 7]
[12, 15, 14, 19, 10, 16, 7]
append est la méthode la plus utilisée de Python. Elle ajoute un élément à la fin de la liste en temps quasi-constant (O(1) pour les intimes). Ne l'oubliez pas : elle sauve des vies. Ou au moins des notes de bas de page.

2.2 Tranches de liste (slicing)

Le slicing permet d'extraire une sous-liste. La syntaxe : liste[début:fin:pas].

nombres = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(nombres[2:6])  # [2, 3, 4, 5]     — de l'indice 2 à 5 inclus
print(nombres[:4])   # [0, 1, 2, 3]     — du début à l'indice 3
print(nombres[6:])   # [6, 7, 8, 9]     — de l'indice 6 à la fin
print(nombres[::2])  # [0, 2, 4, 6, 8]  — un élément sur deux
print(nombres[::-1]) # [9, 8, 7, ..., 0] — la liste à l'envers !
[2, 3, 4, 5]
[0, 1, 2, 3]
[6, 7, 8, 9]
[0, 2, 4, 6, 8]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Le slicing est une des fonctionnalités les plus élégantes de Python. Peu de langages offrent une syntaxe aussi expressive pour découper des tableaux. Elle a été inspirée par le langage APL (1964), un langage tellement compact qu'il utilisait ses propres caractères spéciaux — y compris des symboles grecs. Les programmeurs APL avaient besoin d'un clavier spécial. On est bien en Python.

2.3 Listes hétérogènes (oui, on peut)

# Une liste peut contenir n'importe quoi
fourre_tout = [42, "Python", 3.14, True, [1, 2, 3]]
print(fourre_tout[1])  # "Python"
print(fourre_tout[4][0])  # 1 — une liste dans une liste
Python
1
En Python, vous pouvez mettre des listes dans des listes. C'est des listes inception. Bwoooong. On appelle ça des listes imbriquées (ou tableaux à 2 dimensions). Mais ça, c'est pour le cours 3. Pour l'instant, restons à une dimension. On marche avant de courir.

§3 La boucle for : répéter sans se fatiguer

La boucle for permet de parcourir chaque élément d'une séquence (liste, chaîne, etc.) et d'exécuter du code pour chacun. C'est le couteau suisse du programmeur.

notes = [12, 15, 8, 19, 10]

for note in notes:
    print("Note :", note)
Note : 12
Note : 15
Note : 8
Note : 19
Note : 10
Lisez-le à voix haute : « Pour chaque note dans notes, affiche la note ». C'est quasiment du français. Python, parfois, c'est presque trop facile. On se demande où est l'embrouille. (Spoiler : l'embrouille arrive au §4.)

3.1 range() — générer des séquences numériques

range(n) génère les entiers de 0 à n-1. Utile quand on veut répéter une action un nombre précis de fois, ou quand on a besoin des indices.

# Afficher les nombres de 0 à 4
for i in range(5):
    print(i, "au carré =", i**2)
0 au carré = 0
1 au carré = 1
2 au carré = 4
3 au carré = 9
4 au carré = 16

Variantes de range :

range(3, 8)      # 3, 4, 5, 6, 7  (début, fin — fin exclue)
range(0, 10, 2)  # 0, 2, 4, 6, 8  (début, fin, pas)
range(5, 0, -1)   # 5, 4, 3, 2, 1  (pas négatif = on descend)
range est un générateur paresseux (lazy) : il ne calcule pas tous les nombres d'un coup, il les produit un par un quand on les demande. C'est économe en mémoire. Si vous faites range(10**12), vous ne créez PAS une liste de mille milliards d'entiers — vous créez un objet qui sait les produire à la demande. Malin.

3.2 Parcourir avec les indices : enumerate

Parfois, on a besoin à la fois de l'indice et de la valeur. enumerate fait les deux :

couleurs = ["rouge", "vert", "bleu"]

for i, couleur in enumerate(couleurs):
    print("Couleur n°", i+1, ":", couleur)
Couleur n° 1 : rouge
Couleur n° 2 : vert
Couleur n° 3 : bleu

3.3 Compréhensions de listes — la version hipster

Les compréhensions de listes sont une façon concise de créer une nouvelle liste à partir d'une autre. C'est une spécialité Python : élégant, lisible, efficace.

nombres = [1, 2, 3, 4, 5]

# Version longue (for classique)
carrés_long = []
for n in nombres:
    carrés_long.append(n ** 2)

# Version courte (compréhension)
carrés_court = [n ** 2 for n in nombres]

print(carrés_court)  # [1, 4, 9, 16, 25]

# Avec un filtre (if)
pairs = [n for n in nombres if n % 2 == 0]
print(pairs)  # [2, 4]
[1, 4, 9, 16, 25]
[2, 4]
Les compréhensions de listes sont ce qui se rapproche le plus de la poésie en programmation. Si vous réussissez à écrire une compréhension de liste complexe du premier coup sans erreur, vous avez le droit de vous lever et de faire un tour d'honneur. Les collègues applaudiront.
Les compréhensions de listes viennent du langage fonctionnel Haskell (1990), où on les appelle « list comprehensions ». En Haskell, on peut écrire : [x^2 | x <- [1..5]]. La version Python remplace les barres verticales et les flèches par des mots-clés en anglais — plus accessible, moins intimidant. Un bel exemple d'emprunt entre langages.

§4 La boucle while : répéter... jusqu'à ce que

Si for sert à parcourir une séquence connue, while sert à répéter tant qu'une condition est vraie. On ne sait pas à l'avance combien d'itérations seront nécessaires — on s'arrête quand la condition devient fausse.

for, c'est le métro : vous savez combien de stations. while, c'est la route : vous roulez jusqu'à arriver. Et si vous oubliez la condition d'arrêt, vous faites le tour de la Terre indéfiniment. (On appelle ça une boucle infinie. Ça arrive aux meilleurs. Ctrl+C pour vous échapper.)
# Tant qu'il reste du café, on boit
tasse = 5

while tasse > 0:
    print("☕ Il reste", tasse, "tasses. On boit.")
    tasse = tasse - 1

print("Plus de café. Allons dormir.")
☕ Il reste 5 tasses. On boit.
☕ Il reste 4 tasses. On boit.
☕ Il reste 3 tasses. On boit.
☕ Il reste 2 tasses. On boit.
☕ Il reste 1 tasses. On boit.
Plus de café. Allons dormir.
Piège n°1 : la condition qui ne devient jamais fausse. Si vous oubliez de modifier la variable de la boucle (tasse = tasse - 1 ci-dessus), la boucle tourne à l'infini. Python ne vous arrêtera pas. Votre ordinateur non plus. Les boucles infinies, c'est comme les cours de maths à 8h : ça n'en finit pas.

4.1 break et continue — les interrupteurs

# break : on cherche le premier nombre divisible par 7 après 50
n = 50
while True:
    if n % 7 == 0:
        print("Trouvé :", n)
        break           # on sort
    n = n + 1
Trouvé : 56
# continue : afficher tous les nombres de 1 à 10 sauf les multiples de 3
for i in range(1, 11):
    if i % 3 == 0:
        continue       # on saute ce tour
    print(i, end=" ")

# Sortie : 1 2 4 5 7 8 10
1 2 4 5 7 8 10
Règle d'or : n'abusez pas de break et continue. Un break de temps en temps, ça va. Mais si votre code ressemble à un jeu de Tetris avec des break partout, c'est probablement que votre condition de boucle est mal conçue. Les programmeurs débutants abusent de break. Les experts structurent leurs boucles pour ne pas en avoir besoin.

§5 Applications — Manipuler des données réelles

Assez de théorie. Voici des exemples concrets d'utilisation des listes et des boucles dans des contextes mathématiques et scientifiques.

5.1 Polynômes : les listes comme coefficients

Un polynôme P(x) = a₀ + a₁x + a₂x² + ... + aₙxⁿ peut être représenté par une liste de coefficients [a₀, a₁, a₂, ..., aₙ]. L'indice dans la liste correspond au degré du terme.

Les polynômes sont étudiés depuis plus de 4000 ans. Les Babyloniens savaient déjà résoudre des équations du second degré (vers 2000 av. J.-C.). Sans notation algébrique, ils les décrivaient en prose : « Ajoute le carré de la moitié du coefficient au terme constant... ». Aujourd'hui, ça tient en une ligne de Python. Le progrès.

Évaluation d'un polynôme

def evalue(coeffs, x):
    """Évalue le polynôme représenté par coeffs au point x."""
    resultat = 0
    for i, a in enumerate(coeffs):
        resultat = resultat + a * (x ** i)
    return resultat

# P(x) = 1 + 2x + 3x²   (coefficients [1, 2, 3])
p = [1, 2, 3]

print(evalue(p, 0))   # 1
print(evalue(p, 2))   # 1 + 4 + 12 = 17
print(evalue(p, 10))  # 1 + 20 + 300 = 321
1
17
321

Variation : méthode de Horner (plus maligne)

Au lieu de calculer xi à chaque itération (ce qui est coûteux), on peut utiliser la méthode de Horner : on factorise le polynôme pour n'utiliser que des multiplications et des additions. Pour P(x) = 1 + 2x + 3x², on écrit P(x) = 1 + x(2 + 3x).

def evalue_horner(coeffs, x):
    resultat = 0
    for a in reversed(coeffs):
        resultat = resultat * x + a
    return resultat

print(evalue_horner(p, 2))  # 17
17

Pourquoi c'est mieux ? Moins d'opérations, moins d'erreurs d'arrondi. La méthode de Horner date de 1819 mais était déjà connue des mathématiciens chinois au XIIIe siècle (Qin Jiushao). Rien de nouveau sous le soleil algorithmique.

Dérivée d'un polynôme

La dérivée de P(x) = a₀ + a₁x + a₂x² + a₃x³ est P'(x) = a₁ + 2a₂x + 3a₃x². En termes de listes : on décale et on multiplie.

def derive_polynome(coeffs):
    """Retourne la liste des coefficients du polynôme dérivé."""
    return [i * a for i, a in enumerate(coeffs) if i > 0]

# P(x) = 1 + 2x + 3x² + 4x³
p = [1, 2, 3, 4]
p_prime = derive_polynome(p)

print(p_prime)  # [2, 6, 12] → P'(x) = 2 + 6x + 12x²

# Vérifions : P'(1) = 2 + 6 + 12 = 20
print(evalue(p_prime, 1))  # 20
[2, 6, 12]
20
Vous venez de faire de l'analyse mathématique sans papier ni crayon. Votre prof de maths du lycée est soit fier, soit terrifié. Les deux sont acceptables.

Variation : intégration (pour les courageux)

Et si on calculait l'intégrale ? L'intégrale de P(x) est ∫P(x) dx = C + a₀x + (a₁/2)x² + (a₂/3)x³ + ... En code : on insère un coefficient 0 au début (la constante) et on divise chaque terme par son nouveau degré.

def integre_polynome(coeffs, constante=0):
    return [constante] + [a / (i + 1) for i, a in enumerate(coeffs)]

# P(x) = 2 + 6x + 12x² → ∫P = 0 + 2x + 3x² + 4x³
print(integre_polynome([2, 6, 12]))  # [0, 2.0, 3.0, 4.0]
[0, 2.0, 3.0, 4.0]

Dériver puis intégrer (ou l'inverse) vous redonne le polynôme d'origine. Vérifiez !

5.2 Vecteurs : des listes pour les opérations linéaires

En IA et en physique, un vecteur est une liste de nombres (ses composantes). Python permet de faire des calculs vectoriels avec des listes et des boucles.

Somme de deux vecteurs

def somme_vecteurs(u, v):
    """Additionne deux vecteurs composante par composante."""
    return [u[i] + v[i] for i in range(len(u))]

u = [1, 2, 3]
v = [4, 5, 6]

print(somme_vecteurs(u, v))  # [5, 7, 9]
[5, 7, 9]
Piège n°2 : u + v ne fait PAS l'addition composante par composante en Python ! Il concatène les listes : [1, 2, 3, 4, 5, 6]. C'est une source d'erreur classique. N'écrivez jamais u + v pour additionner des vecteurs. Utilisez votre fonction ou, mieux, NumPy (bibliothèque qu'on verra plus tard).

Produit scalaire

def produit_scalaire(u, v):
    """Calcule le produit scalaire de u et v : Σ u[i] × v[i]"""
    total = 0
    for i in range(len(u)):
        total = total + u[i] * v[i]
    return total

u = [1, 2, 3]
v = [4, 5, 6]

print(produit_scalaire(u, v))  # 1×4 + 2×5 + 3×6 = 4 + 10 + 18 = 32
32
Le produit scalaire est partout en IA : il mesure la similarité entre deux vecteurs. Dans les réseaux de neurones, chaque neurone calcule un produit scalaire entre ses entrées et ses poids. C'est littéralement l'opération la plus importante du machine learning. Sans produit scalaire, pas d'IA. Merci Pythagore (oui, c'est lié).

Norme d'un vecteur

import math

def norme(v):
    """Retourne la norme euclidienne ‖v‖ = √(Σ v[i]²)"""
    somme = 0
    for composante in v:
        somme = somme + composante ** 2
    return math.sqrt(somme)

v = [3, 4]
print(norme(v))  # 5.0  (triangle 3-4-5, vous vous souvenez ?)
5.0
3-4-5, le triangle rectangle préféré des professeurs de maths. Pythagore serait fier. Mais il serait surtout choqué qu'on calcule ça en 3 lignes de Python alors qu'il a dû le faire sur du papyrus.

5.3 Signaux et courbes : normalisation, lissage

Un signal (audio, bourse, température) peut être représenté par une liste de valeurs échantillonnées dans le temps. On applique des transformations dessus.

Normalisation (mettre entre 0 et 1)

def normalise(signal):
    """Met le signal à l'échelle [0, 1]."""
    min_val = min(signal)
    max_val = max(signal)
    if max_val == min_val:
        return [0] * len(signal)  # signal constant → tout à 0
    return [(v - min_val) / (max_val - min_val) for v in signal]

signal = [2, 5, 3, 8, 1]
print(normalise(signal))
# [(2-1)/(7), (5-1)/7, (3-1)/7, (8-1)/7, (1-1)/7]
# ≈ [0.143, 0.571, 0.286, 1.0, 0.0]
[0.14285714285714285, 0.5714285714285714, 0.2857142857142857, 1.0, 0.0]
La normalisation est une étape obligatoire en IA avant de donner des données à un algorithme. Pourquoi ? Parce que si une feature (ex: âge, 0–100) a des valeurs beaucoup plus grandes qu'une autre (ex: salaire en k€, 20–200), l'algorithme va considérer que la première est plus importante, juste à cause de l'échelle. La normalisation égalise les chances. Équitable. 🤝

Moyenne mobile (lissage)

def moyenne_mobile(signal, fenetre):
    """Lisse un signal par moyenne mobile sur 'fenetre' points."""
    resultat = []
    for i in range(len(signal) - fenetre + 1):
        morceau = signal[i:i + fenetre]
        moyenne = sum(morceau) / fenetre
        resultat.append(moyenne)
    return resultat

signal = [1, 3, 2, 8, 5, 7, 6, 4]
print(moyenne_mobile(signal, 3))
# [2.0, 4.33, 5.0, 6.67, 6.0, 5.67]
[2.0, 4.333333333333333, 5.0, 6.666666666666667, 6.0, 5.666666666666667]
La moyenne mobile est l'équivalent informatique du lissage à main levée sur un graphique. Les traders l'utilisent pour repérer des tendances boursières. Les météorologues pour lisser les températures. Les photographes pour réduire le bruit. Et vous, pour impressionner vos amis en transformant des listes de nombres en courbes.

Variation : centrer-réduire (standardisation)

La normalisation [0, 1] n'est pas la seule méthode. On peut aussi centrer-réduire (soustraction de la moyenne, division par l'écart-type). C'est ce qu'on utilise en statistiques et en IA quand on veut que les données aient une moyenne nulle et une variance de 1.

def centre_reduit(signal):
    mu = sum(signal) / len(signal)
    variance = sum((x - mu) ** 2 for x in signal) / len(signal)
    ecart_type = math.sqrt(variance)
    if ecart_type == 0:
        return [0] * len(signal)
    return [(x - mu) / ecart_type for x in signal]

signal = [2, 4, 6, 8]
print(centre_reduit(signal))
# moyenne=5, variance=5, écart-type≈2.236
# [-1.34, -0.45, 0.45, 1.34]
[-1.3416407864998738, -0.4472135954999579, 0.4472135954999579, 1.3416407864998738]

5.4 Calcul de fréquences — initialiser un tableau à partir d'un autre

Une opération courante : compter combien de fois chaque valeur apparaît dans une liste. Le résultat est un tableau de fréquences (ou histogramme). On crée une nouvelle liste, initialisée à zéro, qu'on remplit au fur et à mesure.

def frequences(notes, note_max=20):
    """Calcule le nombre d'étudiants ayant obtenu chaque note de 0 à note_max."""
    # On crée un tableau de (note_max+1) zéros
    histo = [0] * (note_max + 1)

    for note in notes:
        histo[note] = histo[note] + 1

    return histo

notes = [12, 15, 8, 12, 15, 19, 12, 10, 15, 8]
histo = frequences(notes)

# histo[8] = 2, histo[10] = 1, histo[12] = 3, histo[15] = 3, histo[19] = 1
for note, effectif in enumerate(histo):
    if effectif > 0:
        print("Note", note, ":", "■" * effectif, "(" + str(effectif) + ")")
Note 8 : ■■ (2)
Note 10 : ■ (1)
Note 12 : ■■■ (3)
Note 15 : ■■■ (3)
Note 19 : ■ (1)
On vient de faire un histogramme avec des bâtons en ASCII. C'est moche, c'est pixelisé, mais ça marche. En IA, on fait ça avec matplotlib et c'est plus joli. Mais le principe est exactement le même : compter les occurrences. Vous venez de réinventer la table de fréquences. Les statisticiens pleurent de joie.

Variation : normaliser en pourcentages

def frequences_relatives(notes, note_max=20):
    histo = frequences(notes, note_max)
    total = len(notes)
    return [effectif / total for effectif in histo]

rel = frequences_relatives(notes)
print(rel[12])  # 0.3 (30% des étudiants ont eu 12)
0.3

Variation : détection de valeurs aberrantes (outliers)

On peut utiliser les fréquences pour repérer des notes impossibles (ex: 25/20) :

def filtrer_notes(notes, note_max=20):
    return [note for note in notes if 0 <= note <= note_max]

notes_sales = [12, 15, -3, 8, 25, 10]
print(filtrer_notes(notes_sales))  # [12, 15, 8, 10] — les deux intrus ont été virés
[12, 15, 8, 10]

Propre, non ? Les compréhensions de listes avec if sont parfaites pour le filtrage.


§6 Pour aller plus loin

6.1 Listes vs tuples : la guerre des parenthèses

Python a aussi les tuples (1, 2, 3) : comme les listes mais immuables (on ne peut pas les modifier). On les utilise pour des données qui ne doivent pas changer (coordonnées GPS, constantes). Les listes sont pour les données qu'on manipule.

6.2 La boucle for avec else ?

Saviez-vous qu'une boucle for peut avoir une clause else ? Elle s'exécute uniquement si la boucle n'a pas été interrompue par break. C'est utile pour les recherches : si on trouve l'élément, break; sinon, on exécute le else. Contre-intuitif mais pratique.

6.3 Listes et mémoire : attention aux copies

Quand on écrit b = a (où a est une liste), b n'est pas une copie : c'est une nouvelle référence vers la même liste. Modifier b modifie aussi a. Pour copier : b = a[:] ou b = a.copy(). Ne l'oubliez pas, ou vous passerez 3 heures à chercher un bug. (Je parle d'expérience.)

a = [1, 2, 3]
b = a          # b pointe vers la même liste que a
b[0] = 999
print(a[0])    # 999  — surprise ! a aussi changé

c = a[:]       # vraie copie
c[0] = 42
print(a[0])    # 999  — a n'a pas changé, ouf
999
999

§7 Le mot de la fin

Vous avez survolé ce qui fait la puissance des langages de programmation moderne : les listes pour structurer les données, les boucles pour les traiter, et tout un tas d'applications (polynômes, vecteurs, signaux). Félicitations.

John von Neumann (1903–1957) — mathématicien hongrois-américain, l'un des pères de l'architecture des ordinateurs modernes (l'architecture dite « de von Neumann »). Il a compris que les données et les instructions devaient être stockées dans la même mémoire, et qu'on pouvait les parcourir séquentiellement. Sans lui, pas de boucles for. Pas de listes. Pas de programme stocké en mémoire. Chaque fois que vous écrivez for x in liste, vous marchez sur les pas d'un génie.
Bilan du cours 2 : Il ne vous reste plus qu'à tout oublier et à utiliser NumPy. Mais ça, c'est le cours 3. En attendant, amusez-vous avec les listes.
© 2026 — laurent.thiry@uha.fr