Les tableaux
Cours complet · OCaml (option informatique), chapitre 5 · prépas MPSI et MP, option informatique
Travailler ce chapitre sur Adloun Exercices corrigés de ce chapitre
<i class="fa-solid fa-compass mr-2" style="color:#9A563B"></i>5.1 Introduction et motivation
La liste (chapitre 2) est la structure naturelle du style fonctionnel, mais elle a un défaut : accéder à son -ème élément coûte un parcours, proportionnel à . Quand on a besoin d'un accès direct — lire ou écrire la case d'indice en temps constant — on emploie le tableau. C'est une structure mutable et de taille fixe, cousine de la liste Python, et qui se marie naturellement avec les boucles du chapitre précédent.
Les tableaux et les fonctions du module Array relèvent des éléments « utilisables après rappel » du programme : en épreuve, leur documentation est fournie. On les présente donc ici avec leur signature, qu'il n'est pas exigé de mémoriser, mais qu'il faut savoir lire et utiliser.
5.2 Créer et lire un tableau
Un tableau littéral s'écrit entre [| et |], éléments séparés par des points-virgules. Son type est 'a array, et comme une liste il est homogène.
# let t = [| 10; 20; 30 |];;
val t : int array = [|10; 20; 30|]
# t.(0);;
- : int = 10
# Array.length t;;
- : int = 3
On accède à la case d'indice i par t.(i), en temps constant. Les indices vont de 0 à Array.length t - 1.
Un accès t.(i) avec i hors de l'intervalle lève l'exception Invalid_argument "index out of bounds" à l'exécution. Comme en Python, la moitié des bogues sur les tableaux sont des erreurs d'indice : on relit chaque borne en se demandant « la dernière case est-elle t.(n) ou t.(n-1) ? » — c'est t.(n-1).
5.3 Modifier un tableau
C'est la grande différence avec la liste : un tableau est mutable. L'instruction t.(i) <- v remplace le contenu de la case i par v ; sa valeur est ().
# let t = [| 10; 20; 30 |];;
# t.(1) <- 99;;
- : unit = ()
# t;;
- : int array = [|10; 99; 30|]
5.3.1 Construire un tableau
Array.make n v: int -> 'a -> 'a array— un tableau dencases, toutes initialisées àv;Array.init n f: int -> (int -> 'a) -> 'a array— un tableau dencases, la caseivalantf i;Array.copy t: 'a array -> 'a array— un nouveau tableau qui contient les mêmes éléments quet: copie superficielle, donc pour une matrice les lignes restent partagées (copier chaque ligne avecArray.map Array.copy m).
# Array.make 4 0;;
- : int array = [|0; 0; 0; 0|]
# Array.init 5 (fun i -> i * i);;
- : int array = [|0; 1; 4; 9; 16|]
Array.make n v place la même valeur v dans toutes les cases. Pour une valeur immuable (un entier), aucun problème. Mais si v est elle-même mutable — typiquement un tableau — les n cases pointent vers le même objet : modifier l'une les modifie toutes. Ainsi Array.make 2 (Array.make 3 0) crée deux lignes qui sont le même tableau. Pour un tableau de tableaux, on utilise Array.make_matrix (voir plus loin) ou Array.init.
5.4 Parcourir un tableau
Le parcours par indices avec une boucle for est l'idiome de base : il donne accès à l'indice et à la valeur, et permet la modification en place.
let somme t =
let s = ref 0 in
for i = 0 to Array.length t - 1 do
s := !s + t.(i)
done;
!s
On accumule dans une référence en parcourant les indices 0 à Array.length t - 1 inclus. (Le module Array n'offre pas de fonction de sommation au programme : on écrit la boucle.)
Quand on n'a pas besoin des indices, ces fonctions d'ordre supérieur sont plus concises :
Array.iter f t: ('a -> unit) -> 'a array -> unit— appliquefà chaque case pour son effet ;Array.map f t: ('a -> 'b) -> 'a array -> 'b array— le tableau desf t.(i)(nouveau tableau) ;Array.mem x t: 'a -> 'a array -> bool—xfigure-t-il danst?Array.exists p tetArray.for_all p t: ('a -> bool) -> 'a array -> bool— un élément vérifiep? / tous le vérifient ?
# Array.map (fun x -> x * x) [| 1; 2; 3 |];;
- : int array = [|1; 4; 9|]
# Array.for_all (fun x -> x > 0) [| 1; 2; 3 |];;
- : bool = true
5.5 Les matrices (tableaux à deux dimensions)
Une matrice est un tableau de tableaux. On la crée proprement avec Array.make_matrix, qui évite le piège de la valeur partagée.
Array.make_matrix lignes colonnes v : int -> int -> 'a -> 'a array array crée une matrice lignescolonnes dont chaque case (et chaque ligne) est indépendante, toutes initialisées à v.
On accède à la case (ligne i, colonne j) par m.(i).(j), et on la modifie par m.(i).(j) <- v. Le parcours se fait par deux boucles imbriquées.
let table_addition n =
let m = Array.make_matrix (n + 1) (n + 1) 0 in
for i = 0 to n do
for j = 0 to n do
m.(i).(j) <- i + j
done
done;
m
La boucle externe parcourt les lignes, l'interne les colonnes. make_matrix garantit des lignes distinctes — avec Array.make (n+1) (Array.make (n+1) 0), toutes les lignes auraient été le même tableau, et le remplissage aurait été faux.
5.6 Tableaux et listes : que choisir ?
| Liste `'a list` | Tableau `'a array` | |
|---|---|---|
| Accès à l'indice | (parcours) | (direct) |
| Modification d'une case | impossible (immuable) | `t.(i) <- v`, |
| Taille | variable (`::` en tête) | fixe à la création |
| Ajout en tête | `x :: l`, | coûteux (recopie) |
| Traitement naturel | récursion / filtrage | boucle `for` sur les indices |
On choisit le tableau quand on a besoin d'accès direct par indice ou de modification en place (algorithmes numériques, matrices, cases à mettre à jour), et la liste quand la taille évolue et qu'on traite les éléments séquentiellement par récursion. Ce ne sont pas des rivales : ce sont deux outils pour deux besoins.
<i class="fa-solid fa-dumbbell mr-2" style="color:#2E7559"></i>5.7 Exercices résolus
Niveau (application directe du cours)
Donner le type et la valeur affichés.
[| true; false |] ;; Array.length [| 1; 2; 3; 4 |] ;; Array.make 3 5 ;;
Puis : que vaut t après let t = [| 0; 0; 0 |] in t.(2) <- 7; t ?
Démonstration
- : bool array = [|true; false|]
- : int = 4
- : int array = [|5; 5; 5|]
Pour la dernière : t.(2) <- 7 modifie la case d'indice , donc t vaut [|0; 0; 7|].
Écrire somme t avec une boucle for.
Démonstration
let somme t =
let s = ref 0 in
for i = 0 to Array.length t - 1 do
s := !s + t.(i)
done;
!s
La borne haute est Array.length t - 1 (dernier indice valide). Sur le tableau vide, la boucle ne tourne pas et la somme vaut 0.
Écrire maximum t (précondition : t non vide).
Démonstration
let maximum t =
let m = ref t.(0) in
for i = 1 to Array.length t - 1 do
if t.(i) > !m then m := t.(i)
done;
!m
On initialise le maximum courant à la première case, puis on parcourt à partir de l'indice 1. La précondition « non vide » est nécessaire : sur le tableau vide, t.(0) lèverait Invalid_argument.
Niveau (raisonnement intermédiaire)
Écrire carres n renvoyant [| 0; 1; 4; ...; (n-1)*(n-1) |] en une ligne avec Array.init.
Démonstration
let carres n = Array.init n (fun i -> i * i)
Array.init n f construit un tableau de n cases, la case i valant f i. Ici carres 5 donne [|0; 1; 4; 9; 16|]. C'est plus concis et plus sûr qu'une boucle de remplissage.
Écrire renverse t qui inverse t sur place (sans créer de nouveau tableau).
Démonstration
let renverse t =
let n = Array.length t in
for i = 0 to n / 2 - 1 do
let tmp = t.(i) in
t.(i) <- t.(n - 1 - i);
t.(n - 1 - i) <- tmp
done
On échange la case i avec sa symétrique n-1-i, pour i allant jusqu'au milieu (n / 2 - 1) : au-delà, on déferait les échanges. La variable tmp retient une valeur le temps de l'échange. La fonction renvoie () : elle agit par effet de bord.
Écrire indice x t renvoyant Some i (première position de x) ou None, par une boucle qui s'arrête dès qu'on a trouvé.
Démonstration
let indice x t =
let n = Array.length t in
let i = ref 0 and trouve = ref None in
while !i < n && !trouve = None do
if t.(!i) = x then trouve := Some !i;
i := !i + 1
done;
!trouve
La condition !i < n && !trouve = None (paresseuse) garantit deux choses : on ne déborde pas, et on cesse dès la première occurrence. Le type option évite une valeur sentinelle comme -1.
À l'aide de Array.for_all, Array.exists et Array.map, écrire tous_positifs t, contient_zero t et doubles t (tableau des doubles).
Démonstration
let tous_positifs t = Array.for_all (fun x -> x > 0) t
let contient_zero t = Array.exists (fun x -> x = 0) t
let doubles t = Array.map (fun x -> 2 * x) t
Ces fonctions d'ordre supérieur dispensent d'écrire la boucle quand l'indice n'importe pas. map renvoie un nouveau tableau sans modifier t ; for_all/exists renvoient un booléen.
Niveau (approfondissement)
Donner le contenu final de m dans chaque cas, et expliquer.
(* A *) (* B *)
let m = Array.make 2 (Array.make 2 0) let m = Array.make_matrix 2 2 0
in m.(0).(0) <- 1; m in m.(0).(0) <- 1; m
Démonstration
(A) donne [|[|1; 0|]; [|1; 0|]|] : les deux « lignes » sont le même tableau (créé une seule fois par Array.make 2 0), si bien que modifier m.(0).(0) modifie aussi m.(1).(0).
(B) donne [|[|1; 0|]; [|0; 0|]|] : make_matrix crée des lignes indépendantes, donc seule la case visée change.
(Morale : pour un tableau de tableaux, jamais Array.make n (Array.make ...) ; toujours make_matrix ou Array.init n (fun _ -> Array.make ...).)
Écrire transpose m qui renvoie la transposée d'une matrice d'entiers lignescolonnes (rectangulaire, non vide).
Démonstration
let transpose m =
let lignes = Array.length m in
let colonnes = Array.length m.(0) in
let t = Array.make_matrix colonnes lignes 0 in
for i = 0 to lignes - 1 do
for j = 0 to colonnes - 1 do
t.(j).(i) <- m.(i).(j)
done
done;
t
La transposée a colonnes lignes et lignes colonnes : on crée t de la bonne forme, puis on recopie m.(i).(j) dans t.(j).(i). Le nombre de colonnes se lit sur la première ligne m.(0) (d'où l'hypothèse « rectangulaire non vide »).
Écrire crible n renvoyant un tableau de booléens où la case i indique si i est premier (pour 0 <= i <= n).
Démonstration
let crible n =
let est_premier = Array.make (n + 1) true in
est_premier.(0) <- false;
if n >= 1 then est_premier.(1) <- false;
for d = 2 to n do
if est_premier.(d) then begin
let m = ref (d * d) in
while !m <= n do
est_premier.(!m) <- false;
m := !m + d
done
end
done;
est_premier
On part de « tout est premier », on élimine 0 et 1, puis pour chaque d encore marqué premier, on barre tous ses multiples à partir de dd (les plus petits ont déjà été barrés par des facteurs plus petits). Le tableau de booléens est l'outil idéal : accès et mise à jour en temps constant. (Variant de la boucle while : n - !m, qui décroît de d à chaque tour.)*
- Un tableau
'a array: littéral[| ... |], homogène, taille fixe, mutable. Accèst.(i)et écrituret.(i) <- ven ; indices0àArray.length t - 1(hors borne exception). - Construire :
Array.make n v,Array.init n f,Array.copy t. Piège :Array.make n vpartagev; pour un tableau de tableaux,Array.make_matrix. - Parcours par
for i = 0 to Array.length t - 1. Sans indices :Array.iter,Array.map,Array.mem,Array.exists,Array.for_all(rappel A.2). - Matrices :
Array.make_matrix l c v, accèsm.(i).(j), deux boucles imbriquées. - Tableau vs liste : accès direct et mutation mais taille fixe (tableau) ; immuable, extensible en tête, traité par récursion (liste).
5.8 Exercices d'entraînement
Légende : application directe, raisonnement intermédiaire, approfondissement ; signale un classique incontournable. La numérotation prolonge celle des dix exercices résolus.
Thème A — Accès, écriture, construction.
- [11.] Donner le type et la valeur de
[| 1; 2 |],Array.make 2 true,Array.init 4 (fun i -> i + 1). - [12.] Écrire
remplir t vqui metvdans toutes les cases det(par effet de bord). - [13.] Écrire
copie tsansArray.copy, à l'aide deArray.init.
Thème B — Parcours et accumulation.
- [14.] Écrire
minimum t(précondition : non vide). - [15.] Écrire
compte p t(nombre de cases vérifiant le prédicatp) avec une boucle. - [16.] Écrire
produit_scalaire u vde deux tableaux d'entiers de même longueur.
Thème C — Modifications en place.
- [17.] Écrire
decale_droite tqui décale toutes les cases d'un cran vers la droite (la dernière sort, la première devient0). - [18.] Écrire
tri_bulles tqui trieten place (échanges de voisins mal ordonnés, répétés) ; donner un variant de la boucle externe. - [19.] Écrire
est_trie t(le tableau est-il croissant ?) en s'arrêtant au premier défaut.
Thème D — Matrices.
- [20.] Écrire
somme_matrice m(somme de toutes les cases d'une matrice d'entiers). - [21.] Écrire
identite nrenvoyant la matrice identité (des1sur la diagonale, des0ailleurs). - [22.] Écrire
produit_matriciel a bde deux matrices d'entiers compatibles, avec trois boucles imbriquées.