Les boucles et les tests
Informatique — MATLAB, chapitre 10
Le chapitre précédent a montré comment éviter la plupart des boucles. Celui-ci présente celles qui restent — car il en reste : une simulation où chaque pas dépend du précédent, un algorithme itératif, une lecture de fichiers successifs.
Il montre aussi, chiffres à l’appui, ce que coûte une boucle mal écrite.
10.1 Le test
10.1.1 if, elseif, else
x = 12;
if x > 10
disp('grand')
else
disp('petit')
endgrand
Trois différences avec Python : pas de deux-points, pas d’indentation obligatoire, mais un end qui ferme le bloc. L’indentation reste vivement conseillée pour la lisibilité — MATLAB la corrige d’ailleurs automatiquement par Ctrl+I.
note = 14;
if note >= 16
mention = 'tres bien';
elseif note >= 14
mention = 'bien';
elseif note >= 10
mention = 'passable';
else
mention = 'insuffisant';
end
disp(mention)bien
elseif s’écrit en un seul mot. Écrit en deux (else if), il ouvre un second bloc imbriqué qui exigerait son propre end — d’où une erreur de syntaxe en fin de fichier, loin de sa cause réelle.
if note >= 10
mention = 'passable';
elseif note >= 16 % jamais atteint
mention = 'tres bien';
endUne note de 18 déclenche la première condition et s’arrête là. Ordonnez toujours du plus restrictif au plus général. Aucune erreur ne sera signalée : le programme tournera en donnant des résultats faux.
10.1.2 Dans un if, && et ||
x = 7;
region = 'Nord';
if (x > 5) && strcmp(region, 'Nord')
disp('condition remplie')
endcondition remplie
Deux points à retenir.
&& évalue paresseusement : si la première condition est fausse, la seconde n’est même pas calculée. Cela permet de protéger un test :
if (k <= numel(v)) && (v(k) > 0) % pas d'erreur d'indiceAvec & simple, MATLAB évaluerait v(k) même hors limites, et déclencherait une erreur.
Et l’on compare des chaînes par strcmp, jamais par == : le == compare caractère par caractère et exige des longueurs identiques.
10.2 La boucle for
10.2.1 La forme de base
for k = 1:5
fprintf('%d au carre = %d\n', k, k^2);
end1 au carre = 1
2 au carre = 4
3 au carre = 9
4 au carre = 16
5 au carre = 25
La variable prend successivement chaque valeur de la suite. Elle peut parcourir n’importe quel vecteur, pas seulement 1:n.
for k = A parcourt les colonnes
Sur une matrice, la variable de boucle reçoit une colonne entière à chaque tour, pas un élément.
for c = [1 2; 3 4]
disp(c') % affiche [1 3] puis [2 4]
endC’est cohérent avec le parcours par colonnes du chapitre 2, mais surprend la première fois. Pour parcourir tous les éléments : for k = 1:numel(A).
10.2.2 La préallocation
C’est le point le plus important du chapitre.
n = 20000;
tic
v1 = [];
for k = 1:n
v1(k) = k^2;
end
t1 = toc;
tic
v2 = zeros(1, n);
for k = 1:n
v2(k) = k^2;
end
t2 = toc;
fprintf('sans preallocation : %.4f s\n', t1);
fprintf('avec preallocation : %.4f s\n', t2);
fprintf('rapport : %.1f fois\n', t1/t2);sans preallocation : 0.8412 s
avec preallocation : 0.0089 s
rapport : 94.5 fois
Sans préallocation, MATLAB doit réallouer et recopier tout le tableau à chaque tour, puisqu’il grandit d’un élément. Sur 20 000 itérations, c’est 20 000 recopies de taille croissante.
Avec zeros(1, n), la mémoire est réservée une fois pour toutes ; la boucle ne fait qu’écrire dedans.
La règle est absolue : déclarez la taille du résultat avant toute boucle qui le remplit. zeros, ones ou nan(1, n) selon le cas — cette dernière étant préférable, car une case non remplie reste visiblement NaN au lieu de passer pour un zéro légitime.
tic et toc mesurent le temps écoulé : le moyen le plus simple de comparer deux écritures.
10.2.3 Boucle ou vectorisation ?
n = 200000;
x = rand(1, n);
tic
s1 = 0;
for k = 1:n
s1 = s1 + x(k)^2;
end
t1 = toc;
tic
s2 = sum(x.^2);
t2 = toc;
fprintf('boucle : %.4f s\n', t1);
fprintf('vectorise : %.4f s\n', t2);
fprintf('ecart : %.0f fois\n', t1/t2);boucle : 0.1523 s
vectorise : 0.0012 s
ecart : 127 fois
| Situation | Écriture |
|---|---|
| même opération sur tous les éléments | vectorisation |
| filtrer, compter, remplacer | indexation logique (chapitre 9) |
| chaque tour dépend du précédent | boucle |
| algorithme itératif jusqu’à convergence | boucle while |
| lire une série de fichiers | boucle |
Avant d’écrire une boucle, posez-vous une question : chaque tour dépend-il du résultat du précédent ?
Si non, il existe presque toujours une écriture vectorisée — plus courte, plus lisible, et cent fois plus rapide.
Si oui, la boucle est légitime. Préallouez, et n’ayez aucun scrupule.
10.3 La boucle while
capital = 1000;
annees = 0;
while capital < 2000
capital = capital * 1.07;
annees = annees + 1;
end
fprintf('%d annees, capital final %.2f\n', annees, capital);11 annees, capital final 2104.85
while convient quand le nombre de tours dépend du calcul lui-même.
10.3.1 Un algorithme itératif
% racine carree de 2 par la methode de Newton
x = 1;
tol = 1e-12;
iter = 0;
maxiter = 100;
while abs(x^2 - 2) > tol && iter < maxiter
x = (x + 2/x) / 2;
iter = iter + 1;
end
fprintf('x = %.15f apres %d iterations\n', x, iter);
fprintf('erreur = %.2e\n', abs(x - sqrt(2)));x = 1.414213562373095 apres 5 iterations
erreur = 0.00e+00
Remarquez la double condition : abs(...) > tol && iter < maxiter.
Sans le compteur, un algorithme qui ne converge pas — mauvaise initialisation, tolérance trop fine, erreur de formule — tournerait indéfiniment.
Toute boucle while doit comporter une limite d’itérations. Et il faut vérifier après coup si l’on est sorti par convergence ou par épuisement :
if iter == maxiter
warning('convergence non atteinte');
endCinq itérations pour douze décimales exactes : la méthode de Newton double le nombre de décimales correctes à chaque tour.
10.4 Sortir ou passer
v = [4 8 15 16 23 42];
for k = 1:numel(v)
if v(k) > 15
fprintf('premiere valeur > 15 : %d (position %d)\n', v(k), k);
break
end
end
for k = 1:numel(v)
if mod(v(k), 2) == 0
continue
end
fprintf('impair : %d\n', v(k));
endpremiere valeur > 15 : 16 (position 4)
impair : 15
impair : 23
break quitte la boucle, continue saute au tour suivant. mod(x, 2) donne le reste de la division par 2 : il vaut 0 pour un nombre pair.
Notez que la première boucle se réduit à find(v > 15, 1) — l’écriture du chapitre 9, plus courte et plus rapide.
10.5 Le switch
methode = 'mediane';
x = [4 8 15 16 23 42];
switch methode
case 'moyenne'
r = mean(x);
case 'mediane'
r = median(x);
case {'min', 'minimum'}
r = min(x);
otherwise
error('methode inconnue : %s', methode);
end
fprintf('resultat : %.1f\n', r);resultat : 15.5
switch remplace avantageusement une longue chaîne de elseif quand on compare une même variable à des valeurs fixes. Les accolades permettent plusieurs valeurs pour un même cas, et otherwise attrape le reste.
error plutôt qu’un silence
Le otherwise ci-dessus interrompt le programme avec un message explicite. C’est bien préférable à ne rien faire : une méthode mal orthographiée produirait sinon un r non défini, et l’erreur surgirait plus loin, sans rapport apparent avec sa cause.
Échouer tôt et bruyamment vaut mieux qu’échouer tard et discrètement.
10.6 Un exemple complet
rand('seed', 31);
n_sim = 5000;
n_pas = 250;
S0 = 100;
mu = 0.0003;
sigma = 0.012;
finaux = zeros(1, n_sim); % preallocation
for s = 1:n_sim
rendements = mu + sigma * randn(1, n_pas);
trajectoire = S0 * cumprod(1 + rendements);
finaux(s) = trajectoire(end);
end
fprintf('valeur moyenne : %.2f\n', mean(finaux));
fprintf('mediane : %.2f\n', median(finaux));
fprintf('P(perte) : %.1f %%\n', 100*mean(finaux < S0));
fprintf('VaR 95 %% : %.2f\n', S0 - quantile(finaux, 0.05));valeur moyenne : 108.13
mediane : 107.44
P(perte) : 22.9 %
VaR 95 % : 21.36
La boucle est ici légitime : chaque simulation est indépendante, mais le vecteur finaux doit être rempli une case à la fois. Notez la préallocation, et le mean(finaux < S0) du chapitre 9 pour la probabilité de perte.
À l’intérieur, tout est vectorisé : les 250 pas d’une trajectoire se calculent d’un coup par cumprod, sans boucle imbriquée. C’est le bon équilibre — une boucle sur ce qui doit l’être, du vectoriel partout ailleurs.
À vous
Écrivez une boucle qui calcule la suite de Fibonacci jusqu’au terme dépassant 1000, en préallouant, puis affichez le rapport des deux derniers termes.
% a completermaxn = 20;
f = nan(1, maxn); % nan plutot que zeros : les cases vides se voient
f(1) = 1;
f(2) = 1;
k = 2;
while f(k) <= 1000 && k < maxn
k = k + 1;
f(k) = f(k-1) + f(k-2);
end
f = f(1:k);
disp(f)
fprintf('%d termes, dernier = %d\n', k, f(end));
fprintf('rapport : %.6f\n', f(end)/f(end-1));
fprintf('nombre d''or : %.6f\n', (1+sqrt(5))/2); 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
17 termes, dernier = 1597
rapport : 1.618020
nombre d'or : 1.618034
Chaque terme dépend des deux précédents : la boucle est ici incontournable, aucune vectorisation n’est possible.
Deux détails : nan plutôt que zeros pour la préallocation, afin qu’une case non remplie se remarque ; et f = f(1:k) pour couper le surplus. Enfin, '' double dans une chaîne produit une apostrophe simple.
Ce qu’il faut retenir
| Écriture | Effet |
|---|---|
if ... elseif ... else ... end |
test — elseif en un mot, end obligatoire |
| ordre des conditions | du plus restrictif au plus général |
&&, \|\| |
dans un if — évaluation paresseuse |
strcmp(a, b) |
comparer des chaînes, jamais == |
for k = 1:n ... end |
boucle à nombre de tours connu |
for k = A |
parcourt les colonnes de A |
v = zeros(1, n); avant la boucle |
préallocation — facteur 100 |
nan(1, n) |
préallocation plus sûre que zeros |
while cond && iter < maxiter |
boucle avec garde-fou obligatoire |
break, continue |
quitter, passer au tour suivant |
switch ... case ... otherwise |
comparer une variable à des valeurs fixes |
error, warning |
échouer tôt et bruyamment |
tic / toc |
mesurer le temps |
Le dernier chapitre rassemble tout ceci dans des scripts et des fonctions : comment organiser un programme qu’on pourra relire, corriger et réutiliser.