for i=0 to 5 do
print_int i
done;
print_newline()
012345
- : unit = ()
Exercice
Combien de fois est répété for i=a to b do ...
? (c'est-à-dire : combien y a t-il d'entiers de $a$ à $b$ inclus?)
Solution
$b - a + 1$ fois. Ainsi, sur la boucle for i=0 to 5 do
précédente il y a bien 6 chiffres affichés.
Exercice
Écrire une fonction pour calculer la somme des carrés des $n$ premiers entiers en utilisant un for
, puis une fonction récursive.
Solution
let somme_carre n =
let res = ref 0 in
for i=1 to n do
res := !res + i*i
done;
!res in
somme_carre 10;;
let rec somme_carre n =
if n = 1 then 1
else n*n + somme_carre (n-1)
in somme_carre 10;;
- : int = 385
- : int = 385
Comme on le voit sur l'exercice précédent, il est en général plus clair et concis d'écrire une fonction récursive en OCaml. De manière générale, il ne faut pas abuser des références et boucles et s'entraîner à penser et écrire récursivement.
Une variante de la boucle for
avec downto
permet d'énumérer "à l'envers" :
for i=5 downto 0 do
print_int i
done;
print_newline()
543210
- : unit = ()
Boucle while¶
Pour répéter instructions
tant que condition
est vraie :
while condition do
instructions
done
En guise d'illustration, considérons l'algorithme d'Euclide pour le calcul du PGCD de deux entiers $a$ et $b$. Cet algorithme consiste à répéter les opérations suivantes tant que $b \neq 0$ :
- Calculer le reste $r$ de la division euclidienne de $a$ par $b$.
- Remplacer $a$ par $b$ et $b$ par $r$.
Quand $b = 0$, on peut montrer que la valeur de $a$ est le PGCD de $a$ et $b$.
Voici le code OCaml correspondant avec une boucle while
:
let pgcd a b =
let q = ref a in
let r = ref b in
while !r <> 0 do
let reste = !q mod !r in
q := !r;
r := reste
done;
!q;;
pgcd 30 12;;
val pgcd : int -> int -> int = <fun>
- : int = 6
Voici ce que cela donnerait avec une fonction récursive (encore une fois c'est beaucoup plus simple en récursif!) :
let rec pgcd a b =
if b = 0 then a
else pgcd b (a mod b);;
pgcd 30 12;;
val pgcd : int -> int -> int = <fun>
- : int = 6
Exercice
Soit $a \in \mathbb{N}$. La suite de Syracuse est définie par $s_0 = a$ et
$$s_{n+1} =
\begin{cases}
\frac{s_n}{2}, \text{si } s_n \text{ est pair}\\
3s_n + 1, \text{sinon}\\
\end{cases}$$
Écrire une fonction temps_vol
ayant $a$ en argument et renvoyant le premier indice $n$ tel que $s_0 = a$ et $s_n = 1$.
Solution
let temps_vol a =
let n = ref 0 in
let sn = ref a in
while !sn <> 1 do
incr n; (* augmente !n de 1 *)
sn := if !sn mod 2 = 0 then !sn/2 else 3* !sn + 1 (* remplace sn par sn+1 *)
done;
!n in
temps_vol 10
- : int = 6
Exercice
L'écrire en récursif
Exceptions¶
Les exceptions sont déclenchées lorsque le programme rencontre un problème :
1/0
Exception: Division_by_zero.
Raised by primitive operation at unknown location
Called from file "toplevel/toploop.ml", line 208, characters 17-27
L'exception ci-dessus est Division_by_zero
, déclenchée lorsque l'on divise par 0.
Il est possible de spécifier le comportement à adopter lors d'une exception :
try 1/0
with Division_by_zero -> 0
- : int = 0
OCaml exécute l'instruction dans le try
(1/0
ici).
Si cette instruction ne déclenche pas l'exception Division_by_zero
, la valeur de l'instruction est renvoyée.
Sinon, C'est l'instruction dans le with
qui est exécutée et renvoyée.
Exercice
Écrire une fonction quotient : int -> int -> int
qui renvoie le quotient a / b
si b
est non-nul, et max_int
sinon.
Solution
let quotient a b =
try a/b
with Division_by_zero -> max_int;;
quotient 7 2;;
quotient 7 0;;
val quotient : int -> int -> int = <fun>
- : int = 3
- : int = 4611686018427387903
Un autre exemple : assert
est une fonction qui vérifie que son argument est true
et déclenche une exception si ce n'est pas le cas. Ceci peut servir à mettre des tests dans le code.
assert (1 = 2)
Exception: Assert_failure ("[2]", 1, 0).
Called from file "toplevel/toploop.ml", line 208, characters 17-27
Il est possible de définir sa propre exception :
exception Overflow;; (* définition d'une exception Overflow qui affiche une chaîne de caractères *)
let add1 n =
if n = max_int then raise Overflow (* pour éviter un dépassement d'entier ù) *)
else n + 1;;
add1 42;;
add1 max_int;;
exception Overflow
val add1 : int -> int = <fun>
- : int = 43
Exception: Overflow.
Raised at file "[13]", line 4, characters 30-38
Called from file "toplevel/toploop.ml", line 208, characters 17-27
failwith
permet de lancer une exception Failure
(déjà définie en OCaml), avec un message d'erreur. Failure
possède un argument (de type string
, chaîne de caractères) permettant de donner des informations sur l'erreur :
try failwith "Détail sur l'erreur"
with Failure e -> print_string e; print_newline ()
Détail sur l'erreur
- : unit = ()
Voici comment sont définies failwith
et Failure
en OCaml:
exception Failure of string;; (* Failure possède un argument qui est de type string *)
let failwith msg = raise (Failure msg);;
exception Failure of string
val failwith : string -> 'a = <fun>
Les exceptions peuvent aussi permettre de sortir d'une boucle (comme un break
de Python). Même si c'est plutôt considéré comme une mauvaise pratique de programmation (car peut rendre le code plus compliqué), il y a certains cas où c'est justifié.
Exercice
En utilisant une exception, écrire une fonction premier qui s'arrête dès qu'on a trouvé un diviseur.
Solution
exception FoundDivisor;;
let premier n =
try
for i=2 to n/2 do (* un diviseur de n est forcément inférieur à n/2 *)
if n mod i = 0 then raise FoundDivisor
done;
true (* renvoyer true si on a pas trouvé de diviseur *)
with FoundDivisor -> false;;
assert (premier 7 && not (premier 8)) (* vérifie que 7 est premier mais pas 8 *)
(* pas d'erreur sur assert donc le test a marché *)
exception FoundDivisor
val premier : int -> bool = <fun>