Ap 678
Ap 678
1 Éléments de cours
Les notions présentées dans ce cours, en particulier les notions de variables et d’affectation,
prendront leur intérêt à partir du chapitre suivant. Il s’agit donc pour l’instant de pratiquer afin
de comprendre ces notions ; l’utilisation de ces notions sera abordée un peu plus tard.
Une variable mutable est déclarée en OCaml de la même manière qu’une variable non mu-
table, par exemple :
# let x : int ref = ref 8 ;;
val x : int ref = {contents = 8}
Le nom de la variable est x, son type est int ref, sa valeur est "un contenant qui contient
8". Le contenu d’une variable mutable x est désigné par !x :
# !x ;;
- : int = 8
Ce contenu peut être utilisé dans n’importe quelle expression compatible avec son type, par
exemple :
# 3 + !x ;;
- : int = 11
1
NB. Attention aux risques de confusion : il arrive souvent que le terme "valeur d’une
variable mutable" désigne en fait la valeur contenue dans cette variable. Il faut donc être attentif
à bien identifier de quoi on parle réellement.
L’opération permettant de modifier la valeur d’une variable mutable est l’affectation, dont
le symbole est := ; par exemple, la suite ci-dessous contient une affectation :
# x ;;
- : int ref = {contents = 8}
# !x ;;
- : int = 8
# x := 15 ;;
- : unit = ()
# x ;;
- : int ref = {contents = 15}
# !x ;;
- : int = 15
On note que la réponse de l’interpréteur, lors de l’affectation de la valeur 15 à la variable x, est :
- : unit = ()
Ceci signifie qu’une affectation n’est pas une expression qui a une valeur en soi : en fait, elle
change la valeur d’une variable mutable, mais elle n’a pas de valeur qui lui est propre, contrai-
rement par exemple à l’expression :
# !x + 3;;
- : int = 18
Une expression qui n’a pas de valeur en soi est une instruction ; en OCaml, son type est unit
et sa valeur est ().
NB. Il est possible de définir des variables mutables de n’importe quel type : dans ce cours,
nous nous restreindrons dans un premier temps aux types de base.
Voici un autre exemple de bloc, qui permet d’échanger le contenu de deux variables mutables
y et z, de type int ref :
# let x : int = !y in
(
2
y := !z;
z := x
)
;;
- : unit = ()
Comme dit précédemment, en toute généralité, un bloc peut éventuellement se terminer par
une expression.
Dans tous les cas, la valeur de la dernière expression définit la valeur du bloc. Dans les
exemples ci-dessus, comme la dernière expression est de type unit, la valeur du bloc est () (voir
aussi l’exemple de saisie contrôlée dans la section consacrée aux entrées-sorties).
Convention d’écriture.
Un bloc est délimité par des parenthèses ouvrantes et fermantes. Afin d’alléger l’écriture, si
le bloc ne contient qu’une instruction, les parenthèses sont omises.
Les instructions d’un bloc sont séparées par un point-virgule, comme par exemple :
(
min := p ;
max := q
)
Le langage OCaml fournit aussi les fonctions de saisie listées dans le tableau suivant :
Signature lecture au clavier
read_line : unit -> string lit une chaîne de caractères
read_int : unit -> int lit un entier
read_float : unit -> float lit un flottant
Par exemple, la fonction ci-dessous a pour objectif de saisir une valeur entière positive au
clavier. Si la valeur saisie n’est pas positive, elle échoue.
let read_positive_int() : int =
let c : int ref = ref 0 in
(
c := read_int() ;
if !c < 0
then failwith("erreur read_positive_int : valeur saisie < 0")
else !c
)
;;
3
# read_positive_int();;
-9
Exception: Failure "erreur read_positive_int : valeur saisie < 0 ".
# read_positive_int();;
8
- : int = 8
La fonction read_char : unit -> char est fournie dans les fonctions utilitaires du fichier
"AP1util.ml" : ce n’est pas une fonction prédéfinie en OCaml.
2 Exercices de TP
Exercice 1
Saisissez ce qui suit, et expliquez ce qui se passe en suivant les (évolutions de) valeurs des
différentes variables.
let a : int ref = ref 8 ;;
let b : int = 5 ;;
let c : int ref = ref 2 ;;
a ;;
b ;;
c ;;
!a ;;
!b ;;
!c ;;
!a + b ;;
a ;;
b ;;
a := !a + b ;;
a ;;
b ;;
b := !a + b ;;
a ;;
b ;;
a := b * !c ;;
a ;;
b ;;
c ;;
c := 3 + !a + 20 ;;
a ;;
c ;;
c := 3 + a + 20 ;;
!a ;;
!c ;;
En particulier, d’où viennent les erreurs détectées par l’interpréteur ?
Exercice 2
Saisissez ce qui suit, et expliquez ce qui se passe en suivant les (évolutions de) valeurs des
différentes variables.
let a : int ref = ref 8 ;;
4
let b : int ref = a ;;
y ;;
inc(3);;
Exercice 3
Écrivez une fonction shift_left qui, étant données trois variables mutables x, y et z de type
int ref, effectue un décalage vers la gauche de ces trois variables. Par exemple, si !x = 3, !y
= 7 et !z = 11, les valeurs des trois variables à la fin de l’exécution de la fonction seront !x =
7, !y = 11 et !z = 3.
NB. le type du résultat de la fonction est unit : en effet, la fonction ne calcule pas de résultat à
proprement parler, elle se contente de modifier le contenu de variables mutables.
Écrivez une fonction shift_right qui, étant données trois variables mutables x, y et z de
type int ref, effectue un décalage vers la droite de ces trois variables.
Exercice 4
Saisissez la suite d’instructions suivantes :
(
print_string("Quel est votre nom : ");
let nom : string = read_line() in
5
(
print_newline() ;
print_string("Bonjour ") ;
print_string(nom);
print_string(",");
print_newline();
print_string("J’espère que vous passez une bonne journée.");
print_newline()
)
)
;;
Exercice 5
Écrivez une fonction récursive print_list(m : int list) : unit qui affiche chaque élément
d’une liste d’entiers suivi par trois tirets. Testez votre fonction et vérifiez l’affichage produit sur
plusieurs exemples.
Créez un fichier test_print.ml contenant la déclaration de plusieurs listes d’entiers, la fonc-
tion print_list, ainsi que les appels de cette fonction sur les listes préalablement déclarées.
Exécutez le contenu du fichier avec la commande #use "test_print.ml" ;;.
Exercice 6
Écrivez, dans un fichier star_line.ml, une fonction qui permet d’afficher une ligne composée
de k caractères * à l’écran (avec k >=1). Exécutez le contenu du fichier avec la commande #use
"star_line.ml" ;;.
Créez un deuxième fichier get_print_name_age.ml, qui contient la ou les fonctions néces-
saires pour demander à l’utilisateur de saisir son prénom et son âge, et qui les affiche à l’écran,
entourés de caractères *. Testez votre code, en utilisant la commande #use. Par exemple, si le
prénom et l’âge saisis sont Éric et 19, le programme affiche :
************
* Éric, 19 *
************
NB. la fonction String.length : string -> int retourne la longueur de la chaîne de caractères
passée en paramètre.
6
Chapitre 7 : Itérativité
Objectifs :
— savoir manipuler les boucles for,
— savoir manipuler les boucles while.
3 Résumé de cours
De même que les fonctions récursives, les instructions itératives, appelées aussi boucles, per-
mettent de décrire des enchaînements de calculs : cf. section 4 pour une courte discussion sur ces
deux mécanismes.
Ces instructions nécessitent la plupart du temps de manipuler des variables mutables (cf.
chapitre 6), qui vont stocker, au fur et à mesure des calculs, les résultats intermédiaires.
Sont étudiées dans ce chapitre deux instructions itératives : la boucle for et la boucle while
du langage OCaml, dont des variantes se retrouvent dans la plupart des langages de progammation.
Ces instructions permettent de décrire des séquences de calculs :
— pour une boucle for, le nombre de calculs effectués est fixé au départ ;
— pour une boucle while, le nombre de calculs n’est pas connu au départ ; la poursuite ou
l’arrêt des calculs est contrôlé par une expression booléenne.
7
Plus généralement, une instruction for s’écrit de la manière suivante :
for nom_indice = valeur_depart to valeur_arrivee
do
bloc_d_instructions
done ;
L’instruction for n’est exécutée que si la valeur entière valeur_depart est inférieure ou
égale à la valeur entière valeur_arrivee ; dans ce cas, l’instruction ou la suite d’instructions
bloc_d_instructions est exécutée pour toutes les valeurs entières successives croissantes com-
prises entre ces deux valeurs extrêmes. Il est à noter que l’indice nom_indice :
— est déclaré par la partie de l’instruction for :
nom_indice = valeur_depart to valeur_arrivee,
— n’est connu et utilisable que dans cette boucle for.
On peut noter aussi que l’intervalle d’itération est fixé, et ne peut être changé en cours
d’exécution.
Il existe aussi la variante suivante :
for nom_indice = valeur_depart downto valeur_arrivee
do
bloc_d_instructions
done ;
L’instruction for n’est alors exécutée que si la valeur entière valeur_depart est supérieure
ou égale à la valeur entière valeur_arrivee ; dans ce cas, l’instruction ou la suite d’instructions
bloc_d_instructions est exécutée pour toutes les valeurs entières successives décroissantes com-
prises entre ces deux valeurs extrêmes.
8
dans s sont les sommes correspondantes des premiers entiers positifs. Le calcul est effectué
tant que l’expression conditionnelle !s < n est vraie.
— en pratique : initialement, !s = 0, et 0 < 12, donc une première itération est effectuée,
à la fin de laquelle !i vaut 1 et !s vaut 1. Afin de savoir s’il faut effectuer une deuxième
itération, l’expression !s < n est évaluée : comme elle vaut true, cette deuxième itération
est effectuée, à la fin de laquelle !i vaut 2, !s vaut 3. Afin de savoir s’il faut effectuer une
troisième itération, l’expression !s < n est évaluée (elle vaut true), etc. Ceci va continuer
jusqu’à la cinquième itération, à la fin de laquelle on a : !i vaut 5, !s vaut 15. Comme
l’expression !s < n vaut false, il n’y aura pas de sixième itération.
— il faut remarquer que si on appelle la fonction avec une valeur du paramètre nulle (n =
0), le résultat de la fonction est (0, 0), car l’instruction while n’est pas exécutée.
Plus généralement, une instruction while s’écrit de la manière suivante :
while expression_conditionnelle
do
bloc_d_instructions
done ;
4 Récursivité vs Itérativité
La récursivité et l’itérativité sont deux mécanismes permettant de décrire et d’effectuer des
enchaînements de calculs. Si les intérêts respectifs de ces deux mécanismes ne sont pas toujours
très évidents sur des exemples simples, on constate souvent que, quand les problèmes traités
deviennent complexes :
— il est souvent plus facile de concevoir en utilisant la récursivité ;
— l’exécution peut être plus efficace en utilisant l’itérativité.
Ce ne sont bien sûr que des remarques générales, qui ne sont pas toujours vraies dans des cas
particuliers.
Plus précisément, la traduction d’une fonction itérative en fonction récursive est directe : cf.
exemple en annexe. En revanche, la traduction d’une fonction récursive en fonction itérative n’est
pas directe en général. Néanmoins, il existe une méthode bien connue de traduction du récursif
vers l’itératif : elle ne sera pas vue durant cet enseignement, mais ultérieurement.
9
5 Conventions d’écriture
Nous avons vu au chapitre 6 qu’un bloc d’instructions est délimité par des parenthèses ; nous
faisons une exception à cette règle dans le cas des boucles for et while, car les mots-clés do et
done jouent le rôle de parenthèses : il est donc inutile d’ajouter des parenthèses pour délimiter
le bloc d’instructions d’une boucle : c’est d’ailleurs ce qui a été fait dans les exemples ci-dessus.
6 Annexe
Considérons à nouveau la fonction sum_int_inf_n.
let sum_int_inf_n(n : int) : int * int =
let s : int ref = ref 0 and i : int ref = ref 0 in
(
while !s < n
do
i := !i + 1 ;
s := !s + !i
done ;
(!i, !s)
)
;;
Sa traduction en fonction récursive est directe ; elle se fait via l’écriture d’une fonction prin-
cipale, qui appelle une fonction récursive :
let rec sum_int_inf_n_rec_aux(n, i, s : int * int * int) : int * int =
if s >= n
then (i, s)
else
let i_suiv : int = i + 1 in
let s_suiv : int = s + i_suiv in
sum_int_inf_n_rec_aux(n, i_suiv, s_suiv)
;;
10
7 Exercices de TD
Exercice 7
Exécutez la fonction mystere1 ci-dessous pour les valeurs -2, 1, 5 ; expliquez ce qu’elle calcule
et comment elle le fait.
let mystere1(n : int) : int =
if n < 0
then failwith "erreur"
else
if n = 0 || n = 1
then 1
else
let x : int ref = ref 1 and y : int ref = ref 1 and a : int ref = ref 0 in
(
for i = 2 to n
do
a := !y ;
y := !x + !y ;
x := !a
done ;
!y
)
;;
Exercice 8
Que fait la fonction ci-dessous ? NB. La fonction rand_int(a, b), présente dans le fichier
AP1util.ml, a pour résultat une valeur entière choisie aléatoirement entre a et b inclus.
let mystere2() : unit =
let valeur : int = rand_int(0, 200) in
let fin : bool ref = ref false and v : int ref = ref 0 in
while not(!fin)
do
print_string("entrez une valeur entiere : ") ;
v := read_int();
if !v < valeur
then print_string("too low")
else
if !v > valeur
then print_string("too high")
else
(
print_string("correct");
fin := true
);
print_newline()
done ;
;;
Exercice 9
On considère une stalactite. On estime qu’elle gagne en moyenne 0,5 cm par siècle, et on souhaite
simuler la croissance de cette stalactite. Pour cela, on considère que la croissance peut varier
aléatoirement d’un siècle à l’autre (en fonction par exemple de la pluviométrie) ; la variation
de taille de la stalagtite est en fait comprise entre 80% et 120% de la taille moyenne gagnée
par siècle. En pratique, la précision de la variation est de deux chiffres après la virgule (NB. la
11
fonction rand_int(a, b), présente dans le fichier AP1util.ml, a pour résultat une valeur entière
choisie aléatoirement entre a et b inclus).
On donne la taille initiale, comprise entre 10 et 50 cm, et on effectue la simulation : la taille
est calculée tous les siècles et elle est affichée tous les 10 siècles seulement. Le calcul s’arrête
quand la stalactite a doublé sa taille initiale.
Le résultat de la fonction est composé du nombre de siècles qui ont été nécessaires au dou-
blement de sa taille, et de la taille atteinte.
Exercice 10
L’objectif de cet exercice d’estimer la valeur du nombre π en utilisant la méthode de Monte-Carlo.
L’idée est la suivante : on génère aléatoirement des points p = (x, y), où x et y appartiennent à
l’intervalle [−n, n[. Cet espace 2D correspond donc à un carré d’aire 4n2 .
Considérons à présent le cercle de rayon n, centré en (0, 0). Parmi les points générés, certains
sont à l’intérieur du cercle, d’autres à l’extérieur. Plus précisément, un point p = (x, y) est à
l’intérieur du cercle si x2 + y 2 ≤ n2 . Notons aussi que le cercle a une aire de π ∗ n2 .
Le ratio entre l’aire du cercle et l’aire du carré est donc de π/4. Autrement dit, un point choisi
aléatoirement dans le carré a une probabilité de π/4 de se trouver dans le cercle. En choisissant
un nombre suffisamment grand de points aléatoires (disons 100000), on peut donc estimer la
valeur de π en calculant le ratio du nombre de points situés à l’intérieur du cercle par rapport
au nombre de points total.
1. La fonction rand_int(a, b) permet d’obtenir une valeur comprise entre a et b. Utilisez
cette fonction afin de créer une fonction generate_point permettant d’obtenir un point
aléatoire dans un carré de longueur 2n, centré en 0, 0 (i.e. x et y appartiennent à [−n, n[).
2. Définissez la fonction is_inside_circle permettant de savoir si un point (x, y) est à situé
à l’intérieur d’un cercle de rayon donné, centré en (0, 0).
3. Définissez la fonction compute_pi qui calcule une approximation de la valeur de π à partir
de n et du nombre de points aléatoires que l’on souhaite générer.
Exercice 11
L’objectif de cet exercice est d’écrire une fonction itérative guess_number : unit -> int qui
"devine" un nombre nb compris entre 1 et 200, choisi par l’utilisateur. Le programme procède de
la manière suivante : il propose un nombre aléatoire compris entre la borne inférieure (initialement
1) et la borne supérieure (initialement 200). Pour chaque proposition, l’utilisateur indique si nb
est inférieur, supérieur, ou bien égal à celui proposé par le programme : les réponses se feront
en saisissant un caractère (’<’, ’>’ ou ’=’). À chaque itération, la borne inférieure ou la borne
supérieure sera mise à jour, réduisant ainsi l’intervalle des possibilités.
Écrivez une fonction get_answer() : char, qui effectue une saisie contrôlée de la réponse
de l’utilisateur.
Écrivez la fonction guess_number en supposant que l’utilisateur joue correctement, c’est-
à-dire qu’il choisit bien un nombre compris entre 1 et 200, et qu’il répond honnêtement aux
propositions faites par le programme.
Dans un second temps, modifiez votre fonction en tenant compte du fait que l’utilisateur
choisit bien un nombre compris entre 1 et 200, mais qu’il pourrait mentir en répondant aux
propositions du programme : dans ce cas, le programme ne peut pas trouver la réponse, mais il
doit détecter la tricherie.
Que faudrait changer, si l’on suppose que l’utilisateur triche sur tous les aspects de son rôle,
c’est-à-dire si, de plus, il n’est même pas sûr qu’il choisisse un nombre compris entre 1 et 200 ?
Modifiez votre code pour faire une recherche dichotomique, qui est plus efficace en général
que la recherche aléatoire. Elle consiste à choisir systématiquement, pour la proposition faite à
l’utiliisateur, la valeur médiane de l’intervalle des possibilités restantes.
12
8 Exercices de TP
Exercice 12
Écrivez une fonction fact : int -> int, qui calcule itérativement la factorielle d’un entier
positif donné n ; si la valeur fournie n est négative, la fonction doit engendrer une erreur.
Exercice 13
Exercice 14
Écrivez une fonction qui affiche les lettres minuscules selon l’ordre alphabétique (les lettres sont
séparées par un espace), puis qui affiche sur la ligne suivante les lettres majuscules selon l’ordre
lexicographique inverse (les lettres sont aussi séparées par un espace).
Exercice 15
Ecrivez une fonction qui, étant donné un entier strictement positif n, affiche la table de multi-
plication de n.
Ecrivez une autre fonction qui, étant donné un entier strictement positif p, affiche les tables
de multiplication des entiers de 1 à p (si p = 10, vous aurez la quatrième de couverture de vos
cahiers du primaire...).
Écrivez une fonction qui a pour résultat un entier strictement positif saisi par l’utilisateur.
Enfin, écrivez une fonction qui demande à l’utilisateur de saisir un entier strictement positif,
et qui affiche toutes les tables de multiplication pour les entiers compris entre 1 et l’entier saisi.
Exercice 16
L’exercice 15 du chapitre 4 portait sur la vérification de la conjecture Syracuse. Écrivez une
fonction itérative permettant de tester cette conjecture.
Exercice 17
Écrivez une fonction qui affiche les 100 premiers entiers strictement positifs qui sont multiples
de 3.
Ecrivez une autre fonction qui affiche tous les multiples de 3 inférieurs strictement à 100.
13
connaît pas le nombre de notes à l’avance, la fonction s’arrêtera lorsque la note saisie vaut -1.
Les valeurs qui ne sont pas comprises entre 0 et 20 ne seront pas prises en compte. La fonction
se termine en affichant le nombre de notes valides, ainsi que la meilleure note, la moins bonne
note, et la moyenne, et elle a pour résultat ces quatre valeurs.
Exercice 19
Simulation de production d’une exploitation laitière. On considère qu’une vache laitière produit
20 litres de lait par jour à 10 % près (i.e. la production varie dans l’intervalle [20 - 10%, 20 +
10%]).
Lorsqu’une vache allaite son veau, ce qui se produit avec une probabilité de un douzième,
seule la moitié de son lait peut être traité, le reste est bu par le veau.
Par ailleurs, chaque jour, un contrôle sanitaire est effectué, et on estime à 0,12 la probabilité
(pour chaque vache) que le lait trait soit impropre à la consommation.
On utilise le type structuré t_cow = {prod : float ; milk : float ; us : float} afin
de représenter la production d’une vache (volume total, volume obtenu par traite, volume utili-
sable).
Écrivez une fonction qui simule la production journalière de lait d’une vache. Le résultat est
de type t_cow, c’est-à-dire qu’il est composé du volume de lait total des n vaches, du volume de
lait obtenu par traite, et du volume de lait exploitable.
Ecrivez une fonction qui, à partir du nombre de vaches d’une exploitation laitière, simule la
production journalière de ces vaches (le résultat est de type t_cow).
Écrivez une fonction qui simule le fonctionnement d’une exploitation de n vaches sur une
durée de 30 jours. Le résultat est de type t_cow (il contient le volume de lait total des vaches,
la production laitière (i.e. le volume obtenu par traite sur l’ensemble du troupeau), et le volume
de lait exploitable obtenu au cours de ces 30 jours).
Une fromagerie située à proximité de l’exploitation utilise le lait pour fabriquer des reblochons.
Il faut 4 litres de lait pour fabriquer un reblochon de 500g.
Écrivez une fonction qui permet à l’utilisateur de saisir le nombre de fromages qu’il veut
fabriquer, et qui détermine par simulation le temps mis pour récolter le volume requis de lait
pour un nombre donné n de vaches.
Exercice 20
Ocaml possède une petite librairie graphique, dont quelques éléments sont expliqués à la fin de
cette feuille.
On reprend la fonction de calcul de π de l’exercice 4. Modifiez la de manière à afficher le
carré, le cercle et chaque point généré. Choisissez des couleurs différentes selon que le point est
à l’intérieur ou à l’extérieur du cercle.
Exercice 21
Écrivez la fonction draw_sin(n : int) : unit qui calcule de manière itérative n+1 valeurs
également réparties entre 0.0 et 2.0 *. pi (où pi est une variable non mutable à définir, qui
correspond à la valeur π), et, pour chacune de ces valeurs, calcule aussi la valeur sin(x). Il faut
noter que pour l’instant, la fonction ne fait rien de ces points qui sont calculés itérativement.
OCaml possède une petite librairie graphique, dont quelques éléments sont décrits à la fin de
cette feuille. Modifiez maintenant votre fonction draw_sin(n : int) : unit, de manière à ce
qu’elle affiche une approximation de la courbe de la fonction sin(x) entre 0.0 et 2.0 *. pi en
n segments.
14
toutes les fonctions du fichier AP1util.ml ainsi que ce qui est nécessaire pour les applications
graphiques.
On peut ouvrir une fenêtre d’affichage avec l’instruction :
open_graph(dx, dy);;
où dx et dy sont deux entiers donnant la taille horizontale et la taille verticale de la fenêtre
(en nombre de pixels). Par exemple, open_graph(1000, 600);; ouvre une fenêtre de taille 1000
(horizontal) par 600 (vertical).
Implicitement, l’origine de la fenêtre est en bas à gauche, et correspond au pixel de coordon-
nées (0, 0). L’autre pixel extrême de la fenêtre a pour coordonnées (dx - 1, dy - 1) (dans
notre exemple, (999, 599)).
L’instruction close_graph();; ferme la fenêtre d’affichage.
L’instruction clear_graph() permet d’effacer la fenêtre d’affichage.
Il existe implicitement un "curseur", qui est positionné à un certain endroit de la fenêtre.
L’instruction moveto(x, y) ;;, où x et y sont deux entiers, positionne le curseur au pixel de
coordonnées (x, y).
L’instruction lineto(x, y);; trace un trait depuis la position courante du curseur jusqu’à
la position (x, y).
Exécutez par exemple la suite d’instructions suivantes :
moveto(100, 100);;
lineto(200, 200);;
lineto(200, 100);;
lineto(300, 100);;
qui trace trois segments, dont un vertical et un horizontal.
Vous pouvez utiliser les couleurs prédéfinies : black, blue, red, green, white, yellow, cyan,
magenta.
La fonction set_color(color) change la couleur courante, qui prend la valeur color (par
défaut, la valeur de la couleur est black).
Pour afficher un point de coordonnées (x,y), vous pouvez utiliser la fonction plot(x,y), ou
la fonction draw_rect(x, y, 2, 2), qui trace un petit rectangle dont le coin inférieur gauche
est au point de coordonnées (x, y) et qui a une longueur et une largeur de 2 pixels.
La fonction draw_rect(x, y, dx, dy : int * int * int * int) : unit trace un rec-
tangle dont les deux points extrêmes sont les points (x, y) et (x + dx, y + dy).
Pour tracer un cercle de centre (x, y) et de rayon r, vous pouvez utiliser la fonction
draw_circle(x, y, r).
15
Chapitre 8 : Tableaux
Objectifs :
— comprendre les tableaux,
— savoir manipuler les tableaux.
10 Résumé de cours
Schématiquement, un tableau permet de représenter une suite de valeurs de même type, par
exemple :
# [| 3 ; 9 ; 10 ; 2 |] ;;
- : int array = [|3; 9; 10; 2|]
L’interpréteur ocaml a reconnu un tableau d’entiers : le type est int array.
Le contenu d’un tableau s’écrit entre les symboles [| et |].
Une variable se déclare de la manière usuelle :
# let v : int array = [|4 ; 10 ; 3 ; 2 ; 7 |] ;;
val v : int array = [|4; 10; 3; 2; 7|]
Attention : contrairement aux listes, les variables de type tableau sont des variables mu-
tables, dans le sens où il est possible de modifier le contenu du tableau (cf. ci-dessous).
La valeur contenue dans la k eme "case" (ou élément) du tableau t s’écrit t.(k), par exemple
pour le tableau v déclaré précédemment :
# v.(0) ;;
- : int = 4
# v.(1) ;;
- : int = 10
# v.(2) ;;
- : int = 3
# v.(3) ;;
- : int = 2
# v.(4) ;;
- : int = 7
# v.(5) ;;
Exception: Invalid_argument "index out of bounds".
Les cases du tableau v sont donc indicées de 0 à 4. De manière générale, les cases d’un tableau
t sont indicées de 0 à n-1, où n est le nombre de cases du tableau t. On voit sur l’exemple ci-dessus
qu’un indice non compris dans cet intervalle engendre une erreur.
Comme un tableau est mutable, il est possible de modifier les valeurs qu’il contient, par
exemple :
# v ;;
- : int array = [|4; 10; 3; 2; 7|]
# v.(2) <- 15 ;;
- : unit = ()
# v ;;
- : int array = [|4; 10; 15; 2; 7|]
Pour les cases d’un tableau, on utilise donc le symbole d’affectation <- et non le symbole
usuel de l’affectation :=.
16
La fonction arr_len, appliquée à un tableau, a pour résultat le nombre d’éléments de ce
tableau :
# arr_len(v) ;;
- : int = 5
Les instructions itératives sont souvent utilisées pour la manipulation de tableaux : voici par
exemple deux fonctions, l’une calculant la somme des valeurs contenues dans un tableau d’entiers,
l’autre recherchant l’indice de la première valeur négative contenue dans un tableau (si elle existe).
17
val find_neg_val_int_array : int array -> int * bool = <fun>
# v ;;
- : int array = [|4; 10; 15; 2; 7|]
# find_neg_val_int_array(v);;
- : int * bool = (5, false)
# let w : int array = [| 3 ; 2 ; -5 ; 0 ; 7 ; -4 |] ;;
val w : int array = [|3; 2; -5; 0; 7; -4|]
# find_neg_val_int_array(w) ;;
- : int * bool = (2, true)
Remarque. Les variables de type "liste" sont des variables non mutables ; les variables de
type "tableau" sont des variables mutables.
11 Exercices de TD
Exercice 22
Que calcule la fonction suivante :
let mystery_1(n : int) : int array =
if n < 0
then failwith "erreur mystery_1 : parametre negatif"
else
let v : int array = arr_make(n + 1, 1) in
(
for i = 1 to n
do v.(i) <- v.(i-1) * i
done ;
v
)
;;
Quels sont les résultats des expressions mystery_1(-1), mystery_1(0), mystery_1(1), mystery_1(2),
mystery_1(5), mystery_1(-15) ?
Exercice 23
Que font les fonctions ci-dessous ? Expliquez précisément ce que fait la fonction mystery_2 en
particulier.
let entry_cont(a, b : int * int) : int =
let the_end : bool ref = ref false and c : int ref = ref 0 in
(
while not(!the_end)
do
c := read_int() ;
if (a <= !c && !c <= b)
then the_end := true
else (print_string("valeur non valide") ; print_newline())
done ;
!c
)
;;
18
c1 := entry_cont(0, 20) ;
print_string("valeur CC2 etudiant ") ; print_int(i) ; print_newline() ;
c2 := entry_cont(0, 20) ;
print_string("valeur CC3 etudiant ") ; print_int(i) ; print_newline() ;
c3 := entry_cont(0, 20) ;
(!c1, !c2, !c3)
)
;;
Exercice 24
Des joueurs jouent à la roulette, leur nombre est nbPlayers. Chaque joueur dispose d’une somme
d’argent ; initialement, cette somme est la même pour tous les joueurs : 1000 euros. L’ensemble des
sommes disponibles à un instant donné est représenté par un tableau fortune, où fortune.(j)
est la somme dont dispose le joueur de numéro j.
1) Écrivez une fonction init dont le résultat est le tableau fortune contenant les sommes
dont disposent les joueurs à l’instant initial.
A chaque tour de jeu, chaque joueur mise une certaine somme sur un certain numéro : la
somme misée est strictement positive, mais elle est inférieure ou égale à la somme dont dispose
le joueur à ce moment ; le numéro choisi est compris entre 0 et 36. Les tableaux number et bet
contiennent respectivement les numéros choisis par les joueurs et les mises des joueurs à ce tour
de jeu.
2) Écrivez une fonction get_int(a, b : int * int) : int qui permet de saisir une va-
leur entière comprise entre les valeurs de a et de b. Écrivez deux fonctions choice_number et
choice_bet permettant de saisir les numéros et les mises choisis par les joueurs (on peut bien
sûr utiliser des fonctions intermédiaires, les saisies devant être contrôlées).
Le tableau total doit contenir, pour chaque numéro possible de la roulette, la somme totale
misée par les joueurs sur ce numéro ; la variable totalBet doit contenir la valeur totale des mises
des joueurs.
3) Ecrivez une fonction compute_bet_per_number permettant de calculer les valeurs conte-
nues par ces variables.
La répartition des mises entre les joueurs se fait de la manière suivante (il n’y a pas de
banque !). On utilise un tableau gain pour stocker les gains et les pertes des joueurs pour ce tour
de jeu. Si le joueur j a joué sur le numéro n, et que ce numéro est gagnant, le gain du joueur j
est : gain.(j) <- (totalBet * bet.(j)) / total.(j). Si le joueur j n’a pas joué le numéro
n, sa perte est égale à sa mise. Si aucun joueur n’a joué le numéro n, les sommes misées sont
conservées et seront partagées entre les gagnants lors du tour de jeu suivant.
4) Écrivez une fonction qui calcule le tableau gain pour ce tour de jeu.
5) Écrivez une fonction qui simule un nombre de tours de jeu donné.
Exercice 25
19
On considère le type t_date défini par : type t_date = {d : int ; m : int ; y : int} ;;,
et le type t_member, défini par : type t_member = {name : string ; birth : t_date} ;;. Le
tableau t de type t_member array contient les noms et les dates de naissance des 20 membres
d’un club sportif.
1) Écrivez une fonction permettant de remplacer le j eme membre du club par un nouveau
membre dont on connaît le nom et la date de naissance.
2) Écrivez une fonction qui compare deux dates, et qui indique si la première date est anté-
rieure à la deuxième date.
3) Écrivez une fonction permettant de trouver le nom et la date de naissance du joueur le
plus jeune. Même question pour le joueur le plus âgé.
12 Exercices de TP
Exercice 26
Vous disposez des deux fonctions
get_float(v_min, v_max : float * float) : float
et get_intpos() : int,
qui permettent de saisir une valeur de type float comprise entre deux valeurs données v_min et
v_max, et de saisir un entier positif n (cf. fichier AP1saisie5.ml sur Updago, et annexe en fin de
document).
1) Écrivez une fonction permettant de saisir et de stocker n valeurs flottantes comprises entre
deux valeurs données v_min et v_max.
2) Écrivez une fonction dont le résultat est la somme, la moyenne, la valeur minimale et la valeur
maximale des n valeurs de type float stockées dans un tableau donné.
3) Écrivez une fonction permettant de saisir un entier positif n, puis de saisir et de stocker n
valeurs de type float comprises entre deux valeurs données v_min et v_max, et dont le résultat
est constitué de ces valeurs, de la somme et de la moyenne de ces valeurs, ainsi que de la valeur
minimale et de la valeur maximale.
Exercice 27
Écrire une fonction mirror qui, appliquée à un tableau t, produit un tableau contenant les
valeurs contenues dans t, mais en ordre inverse. Par exemple, le résultat de mirror([|20; 19;
9; 7; 4|]) est [|4; 7; 9; 19; 20|].
Exercice 28
On cherche à savoir si une valeur v est présente dans un tableau t. Écrivez une fonction, qui
retourne un booléen indiquant si v est présente dans t. Modifiez votre fonction afin qu’elle
retourne en plus un entier correspondant à l’indice de la première case contenant la valeur v, si
elle existe.
Exercice 29
On effectue des lancés successifs avec un dé à 20 faces. On arrête les lancés lorsque l’on obtient la
valeur 20 pour la première fois. On souhaite alors connaître le nombre de lancés effectués, ainsi
que, pour chaque valeur 1, 2, etc, 20, le nombre de fois où l’on a obtenu cette valeur. Écrire une
fonction permettant de réaliser cela. Pour simuler le dé, la fonction rand_int peut être utilisée.
13 Exercices supplémentaires
Exercice 30
Écrivez une fonction shift_left(t : int array) : unit qui fait un décalage "circulaire" d’un
20
tableau vers la gauche, c’est-à-dire par exemple que si t = [|4; 7; 9; 19; 20|] avant appli-
cation de la fonction, t = [|7; 9; 19 ; 20 ; 4|] après application de la fonction. Écrivez une
fonction shift_right qui effectue un décalage circulaire vers la droite.
Exercice 31
On s’intéresse à la variation de la fonction sin sur l’intervalle [−π, π]. Écrivez une fonction
qui, étant donnée une valeur entière strictement positive n, calcule la suite de n+1 points (x,
sin(x)), où les valeurs de x sont régulièrement espacées sur l’intervalle [−π, π].
Exercice 32
Un club de vente s’est formé, afin "d’offrir" à ses membres la possibilité d’acheter certains produits
à des prix préférentiels. Le club a décidé de limiter le nombre de ses membres à 100 (et comme
c’est un club recherché, ce nombre est atteint en permanence), et de proposer exactement 1000
produits à ses membres.
Chaque produit a un nom, une catégorie (Jouet, Parfumerie, Vetement, Livre, Audio-Visuel),
un nombre d’exemplaires en stock, un prix. Chaque membre a un prénom, un nom, une date de
naissance, un solde (indiquant le montant dont il dispose pour acheter des produits).
1) Définissez les types et données nécessaires pour représenter les informations dont le club
a besoin.
2) Écrivez une fonction permettant de savoir si un produit de nom name peut être fourni par
le club, et si oui, qui fournit de plus l’indice du produit dans le tableau de produits.
3) Écrivez une fonction permettant d’ajouter n exemplaires du produit de nom name au stock
du club.
4) Écrivez une fonction permettant de modifier d’un certain taux les prix de tous les articles.
5) Écrivez une fonction permettant de réaliser l’achat par le client de nom c_name de n
exemplaires du produit de nom p_name. Bien sûr, l’achat ne pouvant avoir lieu que si le client
dispose de la somme nécessaire, le résultat de la fonction contiendra en particulier un booléen
indiquant que l’achat a pu être réalisé.
14 Annexe
(* saisie controle d’un float dans l’intervalle [v_min, v_max] *)
let get_float(v_min, v_max : float * float) : float =
let v : float ref = ref 0.0 and thend : bool ref = ref false in
(
while not( !thend)
do
print_string("saisissez une valeur comprise entre ") ;
print_float(v_min) ;
print_string(" et ") ;
print_float(v_max) ;
print_string(" : ") ;
v := read_float() ;
thend := ( !v >= v_min && !v <= v_max)
done ;
!v
)
;;
(* saisie d’un entier positif *)
let get_intpos() : int =
let v : int ref = ref 0 and thend : bool ref = ref false in
(
21
while not( !thend)
do
print_string("saisissez une valeur entiere positive : ") ;
v := read_int() ;
thend := ( !v >= 0)
done ;
!v
)
;;
22