Les exercices proposés sont volontairement nombreux, de manière à ce que chacun puisse
y trouver son compte. Les prendre dans l’ordre. Les exercices ou questions marqués d’une
étoile sont un peu plus difficile et peuvent être passés dans un premier temps. L’étudiant y
reviendra ensuite.
Pour chaque question, il est demandé d’écrire, avant toute chose, une interface (en suivant
le modèle donné en cours). Vous vérifierez ce type avec la réponse de Ocaml : si il y a des
différences réfléchissez un peu .....
Chaque fonction sera testée : cas nominaux, cas erronnés. Mettre les tests et leurs résultats
en commentaire dans votre fichier OCaml. L’exemple de la fonction valeur absolue est donnée
ci-dessous. Dans ce cas, on peut explorer les cas de tests suivants : un entier négatif (-5 par
exemple), 0 et un entier positif (3 par exemple). En effet la spécification d’une telle fonction
(bien connue des mathématiciens) montre bien que le résultat dépend du signe de l’entier.
D’autre part, 0 est un cas de test bien légitime car est-il positif ? négatif ? une erreur dans
la manipulation des signes de comparaison est si vite arrivée ....
(* Interface abs :
type int -> int
arg a
post abs a = valeur absolue de a
tests abs(-5), abs 0, abs 3
*)
let abs a = if a <0 then (-a) else a;;
1. Ecrire une fonction qui teste si son argument est pair. On indique que l’expression a mod b retourne
le reste dans la division entière de a par b.
Remarque : qui teste signifie la plupart du temps qui retourne true si la condition est vraie
et false sinon.
(*Interface est_pair
type : int -> bool
arguments : x
pré-condition : rien
post-condition : le résultat est true si x est pair, false sinon
tests : est_pair(5); est_pair(52);est_pair(-6);est_pair(-5)*)
let est_pair x = x mod 2 = 0;;
Ici pas de probleme particulier sauf peut-être l’utilisation des 2 symboles = qui n’ont pas la même
signification. Le premier est une liaison d’un nom et d’une valeur (celle de l’expression x mod 2 = 0)
et le second est l’opérateur de comparaison.
L’écriture suivante est à rejeter :
2. Ecrire une fonction qui retourne -1 si son argument est négatif, 0 si c’est 0 et 1 si l’argument est positif.
(* Interface signe :
type : int -> int
arguments : x
1
pre : rien
post : signe x = -1 si x est négatif, 0 si x=0 et 1 si x est positif.
Tests : signe 0 ; signe (-6) ; signe 4
*)
let pi = 3.14;;
(* Interface sphere
type float -> float
argument : r le rayon de la spère
precondition : r positif ou nul
postcondition : (sphere r)= volume de la sphere de rayon r
Tests : sphere 3.4
*)
let sphere r = 4. /. 3. *. pi *. r *. r *. r;;
4. Ecrire une fonction qui prend en argument 3 entiers et retourne le plus grand de ces entiers.
*(Interface max3 :
type ’a *’a *’a -> ’a
arguments : (a,b,c)
postcondition : max(a,b,c) = le plus grand des 3 entiers
Tests : (*place 3*)
max3 (3,4,5); max3 (4,3,5);
(*place 2*)
max3 (2,5,4); max3 (4,5,3);
(*place 1*)
max3 (5,4,2); max3 (5, 2,4);
(* 2 égaux *)
max3 (3,3,1)
(3 égaux)
max3 (-6, -6, -6)
*)
let max3 (a,b,c) = if a>= b then if a >= c then a else c
else if b >= c then b else c;;
2
Autre solution : Proposer la solution qui consiste à écrire une fonction max2 (attention la fonction
max prédéfinie est currifyée) qui calcule le plus grand de 2 nombres puis l’utiliser pour écrire max3
Pour les tests, envisagez les différents ordres possibles pour les nombres et compléter pour être exhaustif
avc 3 différents, 2 différents et tous égaux.
Dernière chose : remarquons le type de la fonction : (’a * ’a * ’a* -> ’a. L’opérateur de com-
paraison est polymorphe : on peut appliquer cet opérateur sur tous les types.
5. Ecrire une fonction qui ajoute de part et d’autre d’une chaı̂ne de caractères quelconque la chaı̂ne “!!”
appelée cadre.
Modifier la fonction de manière à ce que le cadre devienne aussi un argument de la fonction (on dit
que l’on abstrait le cadre).
On veut maintenant encadrer dissymétriquement. Introduire les paramètres nécessaires et écrire la
nouvelle fonction.
6. Ecrire une fonction qui détermine le nombre de solutions réelles d’une équation du second degré (0
quand pas de solution réelle, 1 si racine double, 2 sinon).
Une équation de la forme ax2 + bx + c = 0 est parfaitement définie par la donnée des coefficients a,
b et c. Faie remarquer que le coeff a doit être non nul sinon le problème n’a pas de sens (équation du
1er degré). Que faire dans ce cas ? la spec ne dit rien. Par exemple retourner -1 dans un tel cas ou
mieux faire échouer la fonction. Elle n’est pas définie dans ce cas.
(*Interface solutions
type : float*float*float -> int
arguments : (a,b,c) coefficients d’une équation du second degré
pre : a<>0 (sinon ce n’est pas du second degré)
poscondition : solutions(a,b,c)= nombre de solutions r\’eelles
raises : Failure "solutions : premier degre" si a=0
Tests : (*envisager chacun des cas de figure*)
solutions (1., 1.,1.) (* pas de solutions réelle*);
solutions (1., 0. ,-1.) (* 2 racines différentes *)
solutions (1., 2. ,1.) (* racine double*)
(* test erroné *)
solutions (0., 1.3 ,-1.) (*echec*)
*)
3
Exercice 2 (couples)
Le rationnel pq sera représenté par le couple (p, q) de type int*int.
1. Ecrire la fonction inverse ratio qui calcule l’inverse d’un nombre rationnel.
(* Interface inverse_ratio
type : int * int -> int*int
arguments (p,q)
pre : p<>0 et (p,q) rationnel valide
post inverse_rati0 (p,q)=(q,p)
raises : Failure "rationel nul" si p=0
tests : inverse_ratio (4,5) (*test nominal*)
(*tests erronés : on viloe la precondition *)
inverse_ratio (0,3)
inverse_ration (3,0)
*)
let inverse_ratio (p,q) = if p=0 then failwith "rationel nul"
else (q,p);;
Remarquons le type inféré : il est plus général que type attendu. Tant mieux ! On supposera (pré
condition) que q est non nul sinon, le rationnel argument n’est pas correct.
2. Ecrire la fonction qui réalise l’addition de 2 nombres rationnels. On ne cherchera pas à simplifier le
rationnel résultat.
(* Interface inverse_ratio
type : int * int -> int*int
arguments (p,q)
pre : (p1,q1) et (p2, q2) rationnels valides
post retourne la somme des 2 rationnels
*)
let plus_ratio ((p1,q1),(p2,q2)) = (p1*q2 + p2*q1, q1*q2);;
1. Ecrire une fonction récursive u telle que u n calcule le nième terme de la suite (un )n définie par :
u0 = 1 et un = sqrt(un−1 + 2), n > 0. La fonction sqrt de type float -> float existe et calcule la
racine carrée.
Ici nous avons supposé que la fonction n’était définie que sur les entiers positifs. Faire un test avec un
nombre négatif (cas d’erreur)
2. Ecrire une fonction récursive somme qui calcule la somme des n premiers entiers naturels : somme n =
Σi=n
i=1 i
3. Ecrire une fonction récursive carre qui calcule la somme des n premiers carrés : carre n = Σi=n
i=1 i
2
4
4. Ecrire la fonction puissance en utilisant une méthode dichotomique, c’est-à-dire en utilisant les
résultats suivants :
a2k = (a2 )k
a2k+1 = (a2 )k ∗ a
On supposera a et n entiers naturels.
Utilité du let ... in local : on évite ainsi de recalculer 2 fois la même chose.
5. Reprendre la fonction puissance en utilisant les résultats suivants :
a2k = (ak )2
a2k+1 = (ak )2 ∗ a
On supposera a et n entiers naturels.
i=n i
6. Ecrire une fonction récursive somme puis qui calcule Σi=0 x , avec n un naturel quelconque et x un
entier quelconque. Modifier l’écriture de cette fonction de manière à optimiser les calculs.
Pour optimiser les calculs, l’idée est de se souvenir de la dernière puissance calculée car à l’étape
suivante il suffira de multiplier ce terme par a pour obtenir le nouveau terme. Cela revient à calculer
simultanément 2 suites récurrentes définies par :
sp0 = 1 t0 = 1
Exercice 4 (Listes)
5
let rec nb l = match l with
[] -> 0
| _::r -> 1 + (nb r);;
2. Ecrire une fonction qui compte le nombre d’éléments pairs d’une liste d’entiers
3. Ecrire la fonction somme qui fait la somme des nombres d’une liste de flottants.
4. Ecrire la fonction moyenne qui fait la moyenne arithmétique des nombres d’une liste de flottants. Ecrire
une version naive de la fonction et une version plus optimale (penser à calculer somme et nombre
d’éléments en même temps). La fonction float of int convertit un entier en le flottant correspondant
: ainsi l’expression (float of int 2) +. 3.5 est une expression bien typée.
Solution naive et peu efficace car elle parcourt 2 fois la liste
Solution plus judicieuse qui utilise une fonction auxiliaire qui calcule simultanément la somme et le
nombre.
5. Ecrire la fonction intervalle : intervalle (n,m) retourne la liste des entiers compris entre n et m
inclus. Si n est strictement supérieur à m, le résultat calculé est la liste vide.
Généraliser cette fonction en introduisant un pas (supposé positif non nul).
Cas de tests : intervalle (3,4);; intervalle (5,5);; intervalle (5,4);;. Le 2ème cas ne
découle pas directement de la spécification mais plutôt d’un cas aux limites.
6
let rec intervalle (n,m,p) = if n > m then []
else n :: (intervalle (n+p,m,p));;
6. Ecrire les fonctions appartient et place : appartient teste l’appartenance d’un élément à une liste
et place donne la position d’un élément dans une liste (0 si pas dans la liste, n > 0 si l’élément est le
nième)
Pour éviter le test p=0, on peut vérifier au préalable que l’élément recherché est dans la liste. Mais
cela demande un parcours de la liste.
La fonction place sera plus élégamment écrite en utilisant le mécanisme des exceptions.
Si on modifie la spécification de la liste : échec si l’élément n’est pas dans la liste
(* interface place
args e,l
pre e est dans l
post retourne la place de la 1ere
occurrence de e dans l (le 1er a la place 1)
raises échec avec Failure "place : élément absent" si e n’est pas dans l
tests place (3, [4;5;3]) place (4, [4;5;4;3]) place (2, [4;5;3])
*)
let rec place (e,l) =
match l with [] -> failwith "place : élément absent"
7
| x::r -> if x = e then 1
else 1 + (place (e,r));;
7. Ecrire une fonction min qui retourne le plus petit entier d’une liste d’entiers. Peut on l’utiliser avec
une liste de chı̂nes de caractères ?
8. Ecrire une fonction qui élimine les doublons d’une liste (tout élément ne peut alors figurer qu’une seule
fois dans la liste résultat)
On peut utiliser directement List.mem qui est la fonction prédéfinie OCaml pour appartenance d’un
élément à une liste. Elle s’appelle mem et se trouve dans le module List, d’où l’écriture List.mem
9. Faire l’intersection sans doublons de 2 listes sans doublons.
Ce programme atteint bien son objectif uniquement si les 2 listes en argument sont des listes sans
doublons. Si la précondition n’est pas vérifiée, on ne garantit rien quant au résultat.
10. Faire l’union sans doublons de 2 listes sans doublons.
(* interface inserer
type : ’a *’a list -> ’a list
arg e , l
pre : l est une liste triée dans l’ordre croissant
post : retourne la liste triee dans l’ordre croissant composée des éléments de l et de e
tests : inserer (1, [2; 3; 4]) (* [1;2;3;4]*), inserer (3, [2; 4]) (* [2;3;4]*),
inserer (3, [2; 3; 4]) (* [2;3;3;4]*),
inserer (5, [2; 3; 4]) (* [2;3;4;5]*)
*)
8
On peut définir la notion de liste triée de la facon suivante :
pour tout i, j, si i¿j alors l’element de la liste l à la position i est ¿= à l’element de l à la position j.
2. Faire la concaténation de deux listes triées de manière à obtenir une nouvelle liste triée (on parle alors
de fusion).
(* interface inserer
type : ’a list*’a list -> ’a list
arg l1, l2
pre : l1 et l2 sont deux listes triées dans l’ordre croissant
post : retourne la liste triee dans l’ordre croissant composée des éléments de l1 et de l2
tests : fusion ([1;3;4;4],[2;5]) (*[1;2;3;4;4;5]*)
*)
let rec fusion (l1, l2) = match (l1, l2) with
| ([], []) -> []
| ([], _) -> l2
| (_, []) -> l1
| (x1::l’1, x2::l’2) ->
if x1 < x2
then x1::(fusion (l’1, (x2::l’2)))
else x2::(fusion ((x1::l’1), l’2));;