Laboratoire 2: Définitions de fonctions
1. La fonction factorielle
La factorielle \(n!\) d’un entier naturel \(n\) est le produit des nombres entiers strictement
positifs inférieurs ou égaux à
\(n\). Écrire la fonction fact
à un paramètre entier n
qui renvoie \(n!\).
Le résultat attendu à l’utilisation est le suivant :
# fact;;
- : int -> int = <fun>
# fact 0, fact 1, fact 2, fact 3, fact 4, fact 5;;
- : int * int * int * int * int * int = (1, 1, 2, 6, 24, 120)
Note : pour écrire une fonction récursive f
, et donc d’avoir le droit
d’appeler f
dans la définition de f
, le mot clé rec
est à placer juste
avant l’identificateur lors de la liaison globale :
let rec f arg_1 ... arg_n =
(* Corps de la fonction dans lequel des appels à fct sont possibles. *)
2. La suite de Fibonacci
La suite de Fibonacci est une suite d’entiers dans laquelle chaque terme est la somme des deux termes qui le précèdent. Elle commence généralement par les termes 0 et 1 et ses premiers termes sont
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89.
Écrire la fonction fib
à un paramètre entier n qui renvoie le n-ième terme
de la suite de Fibonacci.
Le résultat attendu à l’utilisation est le suivant :
# fib;;
- : int -> int = <fun>
# fib 0, fib 1, fib 2, fib 3, fib 4, fib 5;;
- : int * int * int * int * int * int = (0, 1, 1, 2, 3, 5)
3. Plus grand diviseur commun
Écrire une fonction pgcd
qui, étant données deux entiers, renvoie leur plus grand
diviseur commun. On se servira du fait que pgcd(m, n) vaut :
- n si m = 0
- pgcd(n, m) si m > n
- pgcd(n mod m, m) sinon
Le résultat attendu à l’utilisation est le suivant :
# pgcd;;
- : pgcd : int -> int -> int = <fun>
# pgcd 1 1;;
- : int = 1
# pgcd 20 30;;
- : int = 10
# pgcd 77 34;;
- : int = 1
4. Fonction d’Ackermann-Péter
La fonction d’Ackermann-Péter Ack(m, n) est définie récursivement par Ack(m, n) =
- n + 1 si m = 0
- Ack(m - 1, 1) si m ≥ 1 et n = 0
- Ack(m - 1, Ack(m, n - 1)) sinon
Écrire la fonction ackermann
à deux paramètres entiers m
et n
qui
calcule Ack(m, n).
Le résultat attendu à l’utilisation est le suivant :
# ackermann;;
- : int -> int -> int = <fun>
# ackermann 0 1, ackermann 1 1, ackermann 2 1, ackermann 3 1;;
- : int * int * int * int * int = (2, 3, 5, 13)
# ackermann 0 2, ackermann 1 2, ackermann 2 2, ackermann 3 2;;
- : int * int * int * int = (3, 4, 7, 29)
5. Coefficient binomiaux
Les coefficients binomiaux, définis pour tout entier naturel n et tout entier naturel k inférieur ou égal à n donnent le nombre de sous-ensembles de k éléments d’un ensemble de n éléments. On les note \(\binom{n}{k}\). Ces nombres sont sujets à la formule de Pascal :
\begin{equation*} \binom{n + 1}{k + 1} = \binom{n}{k} + \binom{n}{k + 1}. \end{equation*}
En utilisant la formule de Pascal, écrire la fonction binom
à deux paramètres entiers
n
et k
qui renvoie \(\binom{n}{k}\). On exploitera le fait que \(\binom{n}{0} = 1\)
pour tout entier naturel n et que \(\binom{n}{k} = 0\) si n < k.
Note : binom n k
correspond à \(\binom{n}{k}\)
Le résultat attendu à l’utilisation est le suivant :
# binom;;
- : binom : int -> int -> int = <fun>
# binom 0 0;;
- : int = 1
# binom 1 0, binom 1 1;;
- : int * int = (1, 1)
# binom 2 0, binom 2 1, binom 2 2;;
- : int * int * int = (1, 2, 1)
# binom 3 0, binom 3 1, binom 3 2, binom 3 3;;
- : int * int * int * int = (1, 3, 3, 1)
# binom 4 0, binom 4 1, binom 4 2, binom 4 3, binom 4 4;;
- : int * int * int * int * int = (1, 4, 6, 4, 1)
Remarque : on peut vérifier à l’aide de la fonction factorielle que \begin{equation*} \binom{n}{k} = \frac{n!}{k! (n - k)!}. \end{equation*} Écrire, en une seule (assez grosse) expression un moyen de vérifier que la version précédemment écrite pour le calcul des coefficients binomiaux et la formule ci-dessus donnent les mêmes valeurs pour tous 0 ≤ k ≤ n ≤ 3.
6. Fonctions mutuellement récursives
Écrire les fonctions récursives is_even
et is_odd
sans utiliser le modulo et
avec comme seule information que 0
est pair. La fonction is_even
renvoie true
si son argument entier est pair, et la fonction is_odd
renvoie respectivement true
si son argument entier est impair. Comme le
titre de l’exercice l’indique, les deux fonctions doivent s’appeler l’une l’autre. Il faudra
se baser sur le fait qu’un nombre n est pair si et seulement si n = 0 ou n - 1 est
impair, et qu’un nombre n est impair si et seulement si n ≠ 0 et n - 1 est pair.
# is_even;;
- : int -> bool = <fun>
# is_odd;;
- : int -> bool = <fun>
# is_even 0, is_even 1, is_even 2, is_even 3, is_even 4, is_even 5;;
- : bool * bool * bool * bool * bool * bool =
(true, false, true, false, true, false)
# is_odd 0, is_odd 1, is_odd 2, is_odd 3, is_odd 4, is_odd 5;;
- : bool * bool * bool * bool * bool * bool =
(false, true, false, true, false, true)
Pour rappel, pour définir deux fonctions mutuellement récursives fct1
et fct2
,
le mot-clé and
est nécessaire :
let rec fct1 a1 ... an =
(* Corps de fct1 ou fct1 et fct2 peuvent etre appelees. *)
and fct2 b1 ... bm =
(* Corps de fct2 ou fct1 et fct2 peuvent etre appelees. *)
Ceci se généralise naturellement à un nombre quelconque de fonctions mutuellement récursives
pourvu que le and
soit utilisé conformément à cet exemple pour deux fonctions.
7. Récursivité terminale
Une fonction est récursive terminale si chaque appel récursif qu’elle effectue est la dernière expression à être évaluée (c’est-à-dire, tout appel récursif est en position terminale). La dernière expression d’une fonction récursive terminale doit donc soit être une expression n’impliquant pas un appel à cette même fonction, soit être un simple appel à la fonction, et jamais en un calcul faisant intervenir des appels récursifs.
Par exemple, la fonction suivante possède un appel récursif en position terminal, et un autre appel récursif en position non terminale. Ce n’est donc pas une fonction récursive terminale (tous les appels ne sont pas en position terminale).
let rec f (x : int) : int =
if x = 1 then
0
else if x > 0 then
f (x - 1) (* appel récursif en position terminale *)
else
x * (f (x + 1)) (* appel récursif en position non terminale *)
- Dans vos définitions précédentes (exercices 1 à 6), identifier quels appels récursifs sont en position terminale, et lesquels ne le sont pas. De là, conclure quelle fonction est récursive terminale (s’il y en a).
La récursivité terminale économise de l’espace mémoire car aucun état (sauf l’adresse de la fonction appelante) n’a besoin d’être sauvé sur la pile d’exécution. Cela signifie également que le programmeur n’a pas à craindre l’épuisement de l’espace de pile ou du tas pour des récursions très profondes.
- Écrire une fonction
fact'
à un paramètre entiern
qui calcule n! en faisant appel à une fonction récursive terminale (à deux paramètres). Indice : le 1er paramètre est le rangn
et le 2ème est un accumulateur qui contient la valeur associée au rang précédent.
# fact, fact';;
- : (int -> int) * (int -> int) = (<fun>, <fun>)
# fact 0 = fact' 0, fact 1 = fact' 1, fact 2 = fact' 2, fact 10 = fact' 10;;
- : bool * bool * bool * bool = (true, true, true, true)
- Écrire une version récursive terminale
fib'
de la fonctionfib
à un paramètre entiern
qui renvoie le n-ième terme de la suite de Fibonacci. Indice : la fonctionfib'
fait appel à une fonction récursive terminale à trois paramètres. Le 1er est le rangn
, le 2ème est un accumulateur qui contient la valeur au rangn - 2
et le 3ème est un autre accumulateur qui contient la valeur au rangn - 1
.
# fib, fib';;
- : (int -> int) * (int -> int) = (<fun>, <fun>)
# fib 0 = fib' 0, fib 1 = fib' 1, fib 2 = fib' 2, fib 10 = fib' 10;;
- : bool * bool * bool * bool = (true, true, true, true)
8. Exponentielle
-
Écrire la fonction
exp
à deux paramètres entiersx
etn
qui renvoie \(x^n\). -
Écrire la fonction
exp'
à deux paramètres entiersx
etn
qui renvoie \(x^n\) en étant récursive terminale. -
Écrire la fonction
fast_exp
à deux paramètres entiersx
etn
qui calcule \(x^n\) plus rapidement que le faitexp
. Indice : se baser sur le fait que \(x^{2n}= (x^n)^2\) et \(x^{2n + 1} = (x^n)^2x\). -
Ré-écrire la fonction
exp
pour qu’elle renvoie \(x^n\) ainsi que le nombre d’appels récursifs effectués pour obtenir ce résultat (renvoyer pour cela une paire(res, nb)
oùres
est le résultat de l’exponentiation etnb
est le nombre d’appels récursifs ainsi réalisés). -
Faire de même pour
fast_exp
et comparer. -
[Bonus, Avancé] Refaire les deux questions précédentes mais en comptant cette fois le nombre de multiplications réalisées. Trouver un lien entre le nombre de multiplications faites par
fast_exp
et l’écriture en base deux de n.
# exp;;
- : int -> int -> int = <fun>
# exp';;
- : int -> int -> int = <fun>
# fast_exp;;
- : int -> int -> int = <fun>
# exp' 2 10;;
- : int = 1024
# exp 2 10;;
- : int * int = (1024, 10)
# fast_exp 2 10;;
- : int * int = (1024, 4)
# fast_exp 3 21;;
- : int * int = (10460353203, 5)
9. Sommes doubles
-
Écrire une fonction
sum1
à deux paramètresn
etm
qui renvoie la somme suivante en fonction de n et m : \begin{equation*} \sum_{a = n}^{m} \sum_{b = a}^{m} b. \end{equation*} -
Écrire une fonction
sum2
à un paramètren
qui renvoie la somme suivante en fonction de n : \begin{equation*} \sum_{0\leq k < j \leq n} k / j \end{equation*}
# sum1, sum2;;
- : (int -> int -> int) * (int -> float) = (<fun>, <fun>)
# sum1 1 3, sum1 0 10, sum1 5 10, sum1 5 3;;
- : int * int * int * int = (14, 440, 175, 0)
# sum2 0, sum2 3, sum2 5, sum2 10;;
- : float * float * float * float = (0., 1.5, 5., 22.5)
10. Application partielle et associativité
Soit la fonction add
définie comme suit :
let add x y = x + y
- Quel est le type de
add
? Vérifier ensuite avec le toplevel. - Quel est le type de
add 5
? Vérifier ensuite avec le toplevel.
On définit maintenant la fonction addx
comme suit :
let addx x = fun y -> x + y
- Quel est le type de
addx
? Vérifier ensuite avec le toplevel. - Quel est le type de
addx 5
? Vérifier ensuite avec le toplevel. - Peut-on observer une différence dans le comportement de
addx
etadd
? Pourquoi ?
On définit finalement la fonction add3
comme suit :
let add3 = addx 3
- Quel est le type de
add3
? Vérifier ensuite avec le toplevel - Quel est le type de
add3 5
? Vérifier ensuite avec le toplevel
Pour chacun des appels suivants, dire s’ils produisent un nombre, une fonction, ou une erreur. Vérifier ensuite avec le toplevel.
add 5 1
add 5
(add 5) 1
add (5 1)