☀️ Stage Pré-rentrée · dès le 24 aoûtRéserver ma place →
Majorant
📘 Fiche de cours · 1re année📐 MPSI💻 Informatique Informatique communeNiveau · Sup

Tableaux à deux dimensions et images

Les tableaux 2D sans le piège : listes de listes, le danger d'aliasing de [[0]*c]*l démonté et tracé, le parcours par double boucle, et les images comme matrices de pixels (négatif, seuillage) — avec trois exercices corrigés.

Fiche rédigée par les mentors Majorant — alumni Polytechnique, CentraleSupélec et Mines Paris.

2 définitionsMis à jour le 2026-08-02

Vue d'ensemble

Une image, un plateau de jeu, un tableur, une grille de Sudoku : dès qu'une information s'organise en lignes et colonnes, on a besoin d'un tableau à deux dimensions. En Python, il n'existe pas de type « matrice » : on utilise une liste de listes. C'est simple… mais une seule ligne de code mal écrite crée le bug le plus classique de toute la première année. Ce chapitre te donne les bons réflexes et démonte ce piège une bonne fois pour toutes.

Au programme (info commune, réforme 2021). Représenter un tableau à deux dimensions par une liste de listes ; accéder à un élément par son couple d'indices (ligne, colonne) ; parcourir un tableau 2D par double boucle ; appliquer ces techniques au traitement d'images numériques en niveaux de gris (négatif, seuillage). Complexité d'un parcours complet : .

Prérequis

  • Listes Python : indexation L[i], len(L), opérateur de répétition [0] * n.
  • Boucles for i in range(n) et compréhensions de liste [expr for _ in range(n)].
  • Notion de complexité temporelle en .
🎯 Accompagnement Majorant

Bloqué sur un bug de matrice qui « se remplit toute seule » ? C'est le piège n°1 de l'info en prépa, et il se règle en cinq minutes avec la bonne image mentale. Nos mentors alumni X · Centrale · Mines t'expliquent le fond, pas juste la recette.

Trouver un mentor →

Un tableau 2D, c'est une liste de listes

Définition 1.1 — Tableau à deux dimensions

Un tableau à deux dimensions (ou matrice) est une liste dont chaque élément est lui-même une liste, toutes de même longueur. Si M est un tel tableau, M[i] est la ligne (une liste), et M[i][j] est l'élément situé à la ligne , colonne . Les indices commencent à .

M = [[1, 2, 3],
     [4, 5, 6]]

print(M[0])       # la ligne 0 : [1, 2, 3]
print(M[1][2])    # ligne 1, colonne 2 : 6
print(len(M))     # nombre de lignes : 2
print(len(M[0]))  # nombre de colonnes : 3
🔍 Décryptage ligne par ligne
M = [[1, 2, 3], [4, 5, 6]]On construit une liste de deux listes. M a 2 lignes et 3 colonnes. Visualise une grille : la première ligne contient 1, 2, 3 ; la seconde 4, 5, 6.
M[0]Le premier crochet choisit une ligne entière : ici la liste [1, 2, 3]. Un seul indice = une ligne, pas un nombre.
M[1][2]Deux crochets : d'abord la ligne 1 ([4, 5, 6]), puis dans cette ligne la colonne 2, soit 6. Ordre à retenir : [ligne][colonne].
len(M)Longueur de la liste extérieure = nombre de lignes (2).
len(M[0])Longueur de la première ligne = nombre de colonnes (3). On suppose toutes les lignes de même longueur, donc M[0] suffit.
📝 Le bon réflexe. len(M) = lignes, len(M[0]) = colonnes. Beaucoup d'étudiants inversent : la liste extérieure empile les lignes, chaque liste intérieure est une ligne, donc sa longueur donne les colonnes.

Créer une matrice de zéros — et le piège de l'aliasing

On a très souvent besoin d'une matrice remplie de qu'on remplira ensuite. La bonne façon utilise une compréhension de liste :

nb_lignes = 2
nb_colonnes = 3
M = [[0] * nb_colonnes for _ in range(nb_lignes)]
M[0][0] = 1
print(M)   # [[1, 0, 0], [0, 0, 0]]
🔍 Décryptage ligne par ligne
[0] * nb_colonnesCrée une ligne de nb_colonnes zéros : [0, 0, 0]. La répétition * sur des entiers ne pose aucun problème (un entier n'est jamais partagé de façon dangereuse).
[ ... for _ in range(nb_lignes)]La compréhension réexécute [0] * nb_colonnes à chaque tour de boucle. On obtient donc nb_lignes listes fraîches et indépendantes. Le _ signifie « je n'utilise pas la variable de boucle ».
M[0][0] = 1On modifie une seule case (ligne 0, colonne 0).
print(M)Seule la première ligne change : [[1, 0, 0], [0, 0, 0]]. Chaque ligne est un objet distinct — c'est exactement ce qu'on veut.
⚠ Le piège central : [[0] * c] * l. Il est tentant d'écrire M = [[0] * 3] * 2. Le code ne plante pas, mais toutes les lignes deviennent la même liste : modifier une case en modifie une par ligne. C'est le bug n°1 de l'année. On le dissèque ci-dessous.
M = [[0] * 3] * 2   # PIEGE : ne fais jamais ca
M[0][0] = 1
print(M)            # [[1, 0, 0], [1, 0, 0]]  <-- les DEUX lignes ont change !
print(M[0] is M[1]) # True : c'est le MEME objet liste
🔍 Décryptage ligne par ligne
[0] * 3Crée une ligne [0, 0, 0]. Jusqu'ici tout va bien : il n'existe qu'un seul objet liste en mémoire.
[ ... ] * 2La répétition * 2 ne copie pas la ligne : elle met deux fois la même référence dans la liste extérieure. M[0] et M[1] pointent vers le même objet — c'est ce qu'on appelle l'aliasing (deux noms, un seul objet).
M[0][0] = 1On modifie « la ligne 0 »… mais comme M[0] et M[1] sont le même objet, M[1][0] vaut désormais 1 aussi.
print(M)Sortie [[1, 0, 0], [1, 0, 0]] : la modification s'est propagée à toutes les lignes. Bug silencieux et déroutant.
M[0] is M[1]L'opérateur is teste l'identité (même objet en mémoire). Ici True : preuve que les deux lignes ne sont qu'un seul objet partagé.
Trace de l'aliasing : M = [[0]*3]*2 puis M[0][0] = 1
ÉtapeCe qui se passe en mémoireM[0]M[1]Même objet ?
[0]*3un objet liste L = [0, 0, 0]
[L]*2M contient deux références vers ce même L[0, 0, 0][0, 0, 0]True
M[0][0]=1on écrit dans L (partagé)[1, 0, 0][1, 0, 0] ✓ modifié aussiTrue
📐 Méthode — Créer proprement une matrice
  1. Décide du nombre de lignes l et de colonnes c.
  2. Écris [[valeur] * c for _ in range(l)] : la compréhension garantit une ligne neuve par itération.
  3. Ne jamais utiliser [[valeur] * c] * l : la répétition externe partage la même ligne (aliasing).
  4. En cas de doute, teste M[0] is M[1] : ça doit renvoyer False.
🎯 Accompagnement Majorant

« Pourquoi les listes s'aliasent mais pas les entiers ? » Cette question sur la mémoire revient à tous les concours (Python et OCaml). Nos mentors alumni X · Centrale · Mines te donnent le modèle mental qui rend tout limpide.

Trouver un mentor →

Parcourir un tableau 2D : la double boucle

Pour visiter toutes les cases, on imbrique deux boucles : une sur les lignes i, une sur les colonnes j. Le squelette universel est :

for i in range(len(M)):          # i parcourt les lignes
    for j in range(len(M[0])):   # j parcourt les colonnes
        # ici on traite la case M[i][j]
        ...
🔍 Décryptage ligne par ligne
for i in range(len(M))Boucle externe : i prend successivement l'indice de chaque ligne, de 0 à len(M) - 1.
for j in range(len(M[0]))Boucle interne, imbriquée dans la première : pour chaque ligne i fixée, j balaie tous les indices de colonne, de 0 à len(M[0]) - 1. C'est cette imbrication qui garantit qu'on passe par toutes les cases.
# on traite la case M[i][j]Au cœur des deux boucles, le couple (i, j) désigne une case précise : c'est ici qu'on lit ou écrit M[i][j] (somme, maximum, transformation d'un pixel…). Ce corps s'exécute exactement fois.

Somme de tous les éléments

def somme(M):
    s = 0
    for i in range(len(M)):
        for j in range(len(M[0])):
            s += M[i][j]
    return s

print(somme([[1, 2, 3], [4, 5, 6]]))   # 21
🔍 Décryptage ligne par ligne
s = 0Accumulateur : on part de 0 et on ajoutera chaque case.
for i in range(len(M))Boucle externe : i prend les valeurs 0, 1, … pour chaque ligne.
for j in range(len(M[0]))Boucle interne : pour la ligne i fixée, j balaie toutes les colonnes.
s += M[i][j]On ajoute la case courante au total. Cette ligne s'exécute une fois par case, donc fois.
return sAprès avoir tout visité, on renvoie la somme : pour l'exemple, .
Trace de somme([[1, 2, 3], [4, 5, 6]])
ijM[i][j]s après s += M[i][j]
0011
0123
0236
10410
11515
12621 ✓

Recherche du maximum

def maximum(M):
    m = M[0][0]
    for i in range(len(M)):
        for j in range(len(M[0])):
            if M[i][j] > m:
                m = M[i][j]
    return m

print(maximum([[3, 8, 1], [4, 2, 9]]))   # 9
🔍 Décryptage ligne par ligne
m = M[0][0]On initialise le maximum avec une case qui existe vraiment (la première). Ne jamais partir de 0 : une matrice peut ne contenir que des négatifs.
for i ... for j ...Double boucle : on visite chaque case.
if M[i][j] > m: m = M[i][j]Si la case courante dépasse le maximum provisoire, elle devient le nouveau maximum.
return mÀ la fin, m contient la plus grande valeur : ici 9.

Transposition (échanger lignes et colonnes)

def transpose(M):
    l = len(M)
    c = len(M[0])
    T = [[0] * l for _ in range(c)]   # T a c lignes et l colonnes
    for i in range(l):
        for j in range(c):
            T[j][i] = M[i][j]
    return T

print(transpose([[1, 2, 3], [4, 5, 6]]))   # [[1, 4], [2, 5], [3, 6]]
🔍 Décryptage ligne par ligne
l = len(M) ; c = len(M[0])On mémorise les dimensions de M : l lignes, c colonnes.
T = [[0] * l for _ in range(c)]La transposée a les dimensions inversées : c lignes et l colonnes. On la crée proprement (compréhension, pas d'aliasing).
T[j][i] = M[i][j]Cœur de la transposition : la case (ligne i, colonne j) de M va en (ligne j, colonne i) de T. Les indices sont échangés.
return TRésultat : [[1, 4], [2, 5], [3, 6]]. Ce qui était en ligne devient colonne.
Propriété 2.1 — Coût d'un parcours complet

Parcourir intégralement un tableau à lignes et colonnes par une double boucle exécute le corps fois. Si chaque traitement de case est en , le coût total est , c'est-à-dire proportionnel au nombre de cases. Pour une image de pixels, on parle de .

Application : les images en niveaux de gris

Définition 3.1 — Image en niveaux de gris

Une image en niveaux de gris est un tableau 2D d'entiers compris entre et . Chaque case est un pixel : img[i][j] est l'intensité lumineuse à la ligne , colonne . Convention : noir, blanc, les valeurs intermédiaires sont des gris. La ligne numérote les rangées de pixels du haut vers le bas.

Le négatif

Le négatif inverse les intensités : ce qui est clair devient sombre et inversement. Chaque pixel devient (le noir 0 devient blanc 255, le blanc 255 devient noir 0).

def negatif(img):
    l = len(img)
    c = len(img[0])
    res = [[0] * c for _ in range(l)]
    for i in range(l):
        for j in range(c):
            res[i][j] = 255 - img[i][j]
    return res

print(negatif([[0, 100, 255], [50, 200, 128]]))
# [[255, 155, 0], [205, 55, 127]]
🔍 Décryptage ligne par ligne
l = len(img) ; c = len(img[0])Dimensions de l'image : hauteur (lignes) et largeur (colonnes) en pixels.
res = [[0] * c for _ in range(l)]On crée une nouvelle image vide de même taille (compréhension : pas d'aliasing). On ne modifie pas l'originale.
res[i][j] = 255 - img[i][j]Formule du négatif appliquée à chaque pixel. Comme img[i][j] est entre 0 et 255, 255 - img[i][j] reste entre 0 et 255 : le résultat est une image valide.
return resOn renvoie l'image inversée. Vérifie : , , .
Trace de negatif([[0, 100, 255], [50, 200, 128]]) — chaque pixel devient 255 − p
ijimg[i][j]255 − p
000255
01100155
022550
1050205
1120055
12128127 ✓

Le seuillage (binarisation)

Le seuillage transforme l'image en noir et blanc pur : on fixe un seuil ; tout pixel supérieur ou égal au seuil devient blanc (255), les autres deviennent noirs (0). Utile pour isoler une forme sur un fond.

def seuillage(img, seuil):
    l = len(img)
    c = len(img[0])
    res = [[0] * c for _ in range(l)]
    for i in range(l):
        for j in range(c):
            if img[i][j] >= seuil:
                res[i][j] = 255
            else:
                res[i][j] = 0
    return res

print(seuillage([[30, 120, 200], [90, 128, 255]], 128))
# [[0, 0, 255], [0, 255, 255]]
🔍 Décryptage ligne par ligne
def seuillage(img, seuil)La fonction prend l'image et le seuil de décision (un entier entre 0 et 255).
res = [[0] * c for _ in range(l)]Nouvelle image de même taille, initialisée à 0.
if img[i][j] >= seuil: res[i][j] = 255Pixel assez clair (≥ seuil) → blanc. Le >= inclut la valeur du seuil elle-même : 128 avec seuil 128 devient blanc.
else: res[i][j] = 0Pixel trop sombre (< seuil) → noir. Après seuillage, l'image ne contient plus que 0 et 255.
return resRésultat binarisé. Avec seuil 128 : 30→0, 120→0, 200→255, 90→0, 128→255, 255→255.
Trace de seuillage([[30, 120, 200], [90, 128, 255]], 128) — pixel ≥ 128 ? blanc (255) sinon noir (0)
ijpixelpixel ≥ 128 ?res[i][j]
0030non0
01120non0
02200oui255
1090non0
11128oui (égalité)255
12255oui255 ✓
📝 Complexité. Négatif comme seuillage visitent chaque pixel une fois avec un traitement en : coût . Doubler la largeur ET la hauteur d'une image quadruple le temps de calcul.
⚠ Ne modifie pas l'image en la parcourant si un pixel dépend de ses voisins. Pour le négatif et le seuillage on peut écrire sur place (chaque pixel ne dépend que de lui-même), mais dès qu'un pixel dépend de ses voisins (flou, contours), écrire dans l'image d'origine fausse les calculs suivants : crée toujours une res séparée par sécurité.

Exercices corrigés

Exo 1Compter les cases nullesFacile

Écris une fonction compte_zeros(M) qui renvoie le nombre de cases égales à 0 dans le tableau 2D M. Teste-la sur [[0, 3, 0], [1, 0, 5]] (réponse attendue : 3).

Voir la correction détaillée

On parcourt toutes les cases avec la double boucle et on incrémente un compteur quand la case vaut 0.

def compte_zeros(M):
    n = 0
    for i in range(len(M)):
        for j in range(len(M[0])):
            if M[i][j] == 0:
                n += 1
    return n

print(compte_zeros([[0, 3, 0], [1, 0, 5]]))   # 3

Cases visitées : 0 (compté), 3, 0 (compté), 1, 0 (compté), 5. Trois zéros → n = 3. Coût .

Exo 2Éclaircir une image sans dépasser 255Intermédiaire

Écris eclaircir(img, k) qui ajoute k à chaque pixel, mais plafonne à 255 (un pixel ne peut pas dépasser 255). Renvoie une nouvelle image. Teste sur [[250, 100], [0, 200]] avec k = 30 (attendu : [[255, 130], [30, 230]]).

Voir la correction détaillée

Créer une image résultat propre (compréhension). Pour chaque pixel, calculer p + k ; si ça dépasse 255, mettre 255. La fonction min exprime exactement ce plafond.

def eclaircir(img, k):
    l = len(img)
    c = len(img[0])
    res = [[0] * c for _ in range(l)]
    for i in range(l):
        for j in range(c):
            res[i][j] = min(img[i][j] + k, 255)
    return res

print(eclaircir([[250, 100], [0, 200]], 30))
# [[255, 130], [30, 230]]

250 + 30 = 280 > 255 → plafonné à 255. 100 + 30 = 130, 0 + 30 = 30, 200 + 30 = 230 : tous sous 255, inchangés. Le piège serait d'oublier min(..., 255) et de produire des pixels invalides supérieurs à 255.

Exo 3Somme de chaque ligneDifficile

Écris sommes_lignes(M) qui renvoie une liste contenant la somme de chaque ligne de M. Pour [[1, 2, 3], [4, 5, 6]], le résultat attendu est [6, 15]. Réfléchis bien à la construction de la liste résultat.

Voir la correction détaillée

La sortie est un tableau 1D de longueur « nombre de lignes ». On l'initialise à 0, puis pour chaque ligne on accumule ses colonnes dans res[i].

def sommes_lignes(M):
    res = [0] * len(M)          # une case par ligne (entiers : pas d'aliasing)
    for i in range(len(M)):
        for j in range(len(M[0])):
            res[i] += M[i][j]
    return res

print(sommes_lignes([[1, 2, 3], [4, 5, 6]]))   # [6, 15]

Ligne 0 : 1+2+3 = 6 ; ligne 1 : 4+5+6 = 15 → [6, 15]. Ici [0] * len(M) est correct car ce sont des entiers (immuables) : le piège d'aliasing ne concerne que la répétition de listes. Coût .

Récap final — Ce qu'il faut absolument retenir

Un tableau 2D est une liste de listes ; l'essentiel tient dans la manière de le créer (sans aliasing) et de le parcourir (double boucle). Vérifie que tu sais répondre à tout ceci.

  • Sais-tu dire ce que valent len(M) et len(M[0]) — lignes ou colonnes ?
  • Sais-tu accéder à l'élément ligne , colonne avec le bon ordre des crochets ?
  • Sais-tu créer une matrice de zéros avec [[0] * c for _ in range(l)] ?
  • Sais-tu expliquer pourquoi [[0] * c] * l crée des lignes aliasées et ce que is révèle ?
  • Sais-tu écrire la double boucle qui visite chaque case exactement une fois ?
  • Sais-tu calculer la somme et le maximum d'un tableau 2D (en initialisant le max sur une vraie case) ?
  • Sais-tu qu'une image en niveaux de gris est une matrice d'entiers 0..255 (0 noir, 255 blanc) ?
  • Sais-tu coder le négatif () et le seuillage (≥ seuil → 255 sinon 0) ?
  • Sais-tu que parcourir une image coûte ?

Débloque la fiche complète

Théorèmes, démonstrations à savoir refaire, méthodes-types et pièges de concours : crée ton compte gratuit pour tout lire. Une seule fois pour toutes les fiches et ressources Majorant.

Gratuit · vos données restent confidentielles.

Valide tes acquis

Quiz — Tableaux 2D & images

11 questions · une à la fois · seuil de maîtrise 80 %.

Informatique commune · SupQuiz — Tableaux à deux dimensions et imagesQuestion 1 / 11
FacileChoix unique1 pt

On définit la matrice G ci-dessous. Que valent len(G) et len(G[0]) ?

G = [[1, 2, 3, 4], [5, 6, 7, 8]]
Sélectionne une réponse pour valider.

Fiches associées

📐 MPSI·Informatique

Recherche par dichotomie

La recherche dichotomique dans un tableau trié, expliquée ligne par ligne : code Python commenté, exécution pas à pas, preuve de terminaison et de correction (variant \(d-g\), invariant de boucle), version récursive et complexité \(O(\log n)\).

📐 MPSI·Informatique

Variables, types et affectations

Les briques de base de Python pour bien démarrer la prépa : affectation, types int/float/bool/str, conversions, opérateurs (/ // %), f-strings — avec les pièges classiques et deux exercices corrigés.

📐 MPSI·Informatique

Conditions et booléens

Faire choisir un programme : booléens, comparaisons, if / elif / else, rôle de l'indentation, et combinaison de conditions avec and / or / not — avec pièges et exercices corrigés.

📐 MPSI·Informatique

Boucles for et while

Répéter une action : boucle for avec range, boucle while qui termine, et le motif de l'accumulateur (somme, compteur) — avec table de trace, pièges et exercices corrigés.

📐 MPSI·Informatique

Fonctions : paramètres et valeurs de retour

Définir et appeler une fonction, comprendre paramètres et valeur de retour, et surtout ne jamais confondre return (renvoie) et print (affiche) — avec pièges et exercices corrigés.

📐 MPSI·Informatique

Listes et chaînes de caractères

Créer, indexer, parcourir et découper des listes et des chaînes ; comprendre qu'une liste est modifiable et une chaîne non — avec les pièges d'indices et deux exercices corrigés.

Tu veux aller plus loin sur ce chapitre ?

Nos mentors alumni de Polytechnique, CentraleSupélec et Mines Paris t'accompagnent en cours particuliers — démonstrations détaillées, exos type concours, oraux blancs.

Trouver un mentor →