Lycée Thiers mpsi 123 LISTE D EXERCICES N 1 : CORRECTION PARTIELLE (2/2) EX. 11 En adaptant l exercice précédent, écrire une fonction qui calcule la somme des diviseurs d un entier n N. En déduire la liste des nombres inférieurs ou égaux à 10 000 qui sont égaux à la somme de leurs diviseurs stricts. let somdiv n = let s = ref 0 in for k = 1 to n do if n mod k = 0 then s :=!s + k!s let estparfait n = somdiv n = 2*n let nbparfaitsinferieurs m = for n = 1 to m do if estparfait n then ( print_int n; ) done nbparfaitsinferieurs 10000 6 28 496 8128 - : unit = () Remarque. Ces fonctions (et tout particulièrement somdiv) sont bien trop inefficaces pour rechercher les nombres parfaits de plus grande taille. EX. 12 Ecrire une fonction qui, étant donné n N, renvoie le nombre de chiffres décimaux de n. On envisagera deux méthodes : une formule directe ou bien une boucle. Un peu de maths pour commencer... Notons k le nombre de chiffres d un entier n 1. Alors 10 k 1 n < 10 k et donc k = 1 + log 10 (n). D où la première méthode : let nbchiffres n = if n = 0 then 1 else int_of_float (1. +. floor (log10 (float_of_int n)))
LISTE D EXERCICES N 1 : CORRECTION PARTIELLE (2/2) 2 On a utilisé les fonctions de conversion int_of_float et float_of_int, la fonction log10 qui calcule le logarithme décimal de son argument et la fonction floor (de type float float, attention...) qui calcule la partie entière de son argument. Une seconde solution, consiste à partir de n et à diviser par 10 jusqu à atteindre 0, en comptant le nombre d étapes. Ici, diviser par 10 signifie calculer le quotient de la division euclidienne par 10. Exemple : let nbchiffresbis n = let q = ref n in let count = ref 0 in while!q > 0 do q :=!q / 10; incr count!count 12345 1234 123 12 1 0 : 5 étapes Donnons une dernière solution, plus informatique dans son esprit : on convertit l entier n en une chaîne de caractères, dont on calcule ensuite la longueur : let nbchiffrester n = string_length (string_of_int n) EX. 13 Ecrire une fonction qui affiche les entiers 1, 2,, n 1, n sur une ligne, puis 2, 3,, n, 1 sur la suivante, etc... jusqu à n, 1,, n 2, n 1. Exemple : let cycle n = for ligne = 0 to n-1 do for colonne = 0 to n-1 do print_int (1 + (colonne + ligne) mod n) done cycle 9 123456789 234567891 345678912 456789123 567891234 678912345 789123456 891234567 912345678 - : unit = () EX. 14 Ecrire une fonction fact : int int qui calcule la factorielle d un entier naturel. On rappelle que 0! = 1. En outre, cette fonction devra afficher un message d erreur si n < 0 (utiliser pour cela la fonction prédéfinie failwith). A partir de quelle valeur de n le résultat est-il fantaisiste 1? Pour répondre à cette dernière question, on pourra calculer en parallèle avec le type float : en dépit des erreurs d arrondi, cela permettra de repérer le premier résultat aberrant. 1. Le type int ne permet pas de manipuler des entiers au-delà d un certaine taille.
LISTE D EXERCICES N 1 : CORRECTION PARTIELLE (2/2) 3 let fact n = if (n < 0) then failwith "argument invalide" else let f = ref 1 in for k = 1 to n do f :=!f * k!f fact 5 - : int = 120 Le plus petit entier pour lequel cette fonction renvoie un résultat faux est 21 (résultat négatif) : fact 20 - : int = 2432902008176640000 fact 21 - : int = -4249290049419214848 Attention! Ceci ne vaut pour une architecture 64 bits (sur une architecture 32 bits, le seuil maximal de validité est plus bas). On contrôle avec une version float de la même fonction : let float_fact n = let f = ref 1. in for k = 1 to n do f :=!f *. (float_of_int k)!f float_fact 20 - : float = 2.43290200818e+18 EX. 15 Ecrire une fonction binomial int int qui, étant donnés des entiers k, n tels que 0 k n, renvoie ( n k). On utilisera d abord la formule bien connue : ( ) n n! = k k! (n k)! en faisant intervenir la fonction fact élaborée à l exercice précédent. On reprendra ensuite l écriture de binomial avec la formule simplifiée : ( ) n n (n 1) (n k + 1) = k k! sans utiliser la fonction fact, mais avec une boucle inconditionnelle. Les résultats numériques obtenus avec une version ou l autre sont-ils toujours identiques? Commenter. let binomial n k = (fact n) / ((fact k) * (fact (n-k))) let binom n k = let b = ref 1 in for j = 1 to k do b := (!b * (n - j + 1)) / j!b
LISTE D EXERCICES N 1 : CORRECTION PARTIELLE (2/2) 4 En raison du calcul incorrect de la factorielle (cf. exercice précédent), la fonction binomial échoue par exemple avec les arguments 21 et 10. En revanche, la formule simplifiée permet de retarder l apparition de l overflow : binomial 21 10 - : int = -29335 (résultat aberrant!) binom 21 10 - : int = 352716 (résultat correct) Remarque. Dans la fonction binom, le parenthésage employé ne pourrait pas être remplacé par : b :=!b * ((n - j + 1) / j) Pour quelle raison? EX. 16 Ecrire une fonction qui affiche les n premières lignes du triangle de Pascal. Il serait maladroit de procéder en invoquant plusieurs fois la fonction écrite à l ex. précédent. On utilisera plutôt la formule : ( ) ( ) ( ) n 1 n 1 n + = k 1 k k La bonne idée consiste, pour calculer les premières lignes du triangle de Pascal, à utiliser un vecteur (de type int vect). Initialement égal à [ 1; 0; ; 0 ], ce vecteur sera transformé itérativement : il vaudra successivement [ 1; 1; 0; ; 0 ], puis [ 1; 2; 1; 0; ; 0 ], puis [ 1; 3; 3; 1; 0; ; 0 ] et ainsi de suite. A chaque tour de boucle, on affichera le vecteur au moyen d une fonction print_vect suivante : let print_vect v = let n = vect_length v in for k = 0 to n-1 do print_int v.(k); print_string let triangle_pascal n = let v = make_vect (n+1) 0 in v.(0) <- 1; print_vect v; for i = 1 to n do for j = i downto 1 do v.(j) <- v.(j) + v.(j-1) print_vect v done triangle_pascal 7 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 2 1 0 0 0 0 0 1 3 3 1 0 0 0 0 1 4 6 4 1 0 0 0 1 5 10 10 5 1 0 0 1 6 15 20 15 6 1 0 1 7 21 35 35 21 7 1 - : unit = () La fonction print_vect ci-dessus n est pas terrible, car on souhaiterait obtenir un meilleur alignement des différentes cases du tableau. Une solution meilleure est obtenue avec :
LISTE D EXERCICES N 1 : CORRECTION PARTIELLE (2/2) 5 let print_vect v = let n = vect_length v in for k = 0 to n-1 do printf printf "%4d" v.(k); On a utilisé la fonction printf (accessible au sein du module du même nom) qui permet d effectuer un affichage avec format. Cette fonction est apparue historiquement avec le langage C. Disons simplement que la chaine de format %4d indique que l entier v.(k) sera affiché sur une plage de 4 caractères. En prenant soin de ne pas faire intervenir d entiers comportant plus de trois chiffres décimaux, on est sûr d obtenir un espacement d au moins un caractère entre deux entiers consécutifs ainsi qu un alignement correct : triangle_pascal 10 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 2 1 0 0 0 0 0 0 0 0 1 3 3 1 0 0 0 0 0 0 0 1 4 6 4 1 0 0 0 0 0 0 1 5 10 10 5 1 0 0 0 0 0 1 6 15 20 15 6 1 0 0 0 0 1 7 21 35 35 21 7 1 0 0 0 1 8 28 56 70 56 28 8 1 0 0 1 9 36 84 126 126 84 36 9 1 0 1 10 45 120 210 252 210 120 45 10 1 - unit = () EX. 17 Programmer le calcul du n ème terme de la suite de Fibonacci. On rappelle que : F 0 = 0, F 1 = 1, n N, F n+1 = F n + F n 1 let fib n = if n = 0 then 0 else if n = 1 then 1 else let a = ref 0 and b = ref 1 in for k = 2 to n do let c =!a +!b in a :=!b; b := c!b Le principe est simple : on se donne deux références a et b qui correspondent à deux termes consécutifs de la suite(f n ) n N. A chaque tour de boucle, on avance d un cran : initialement!a = F 0 et!b = F 1. Si pour un certain k, on a en entrant dans le corps de la boucle!a = F k 2 et!b = F k 1, alors on aura en sortant!a = F k 1 et!b = F k 2 + F k 1 = F k. Ceci montre (par récurrence) que l évaluation de l expression fib n donne bien F n pour tout n 2. Quant aux cas particuliers n = 0 et n = 1, il sont correctement traités à part dès le début.
LISTE D EXERCICES N 1 : CORRECTION PARTIELLE (2/2) 6 EX. 18 Prévoir le résultat de l évaluation de : let f x = x + 1 in let g x = 2*x in let u a b x = a (b x) in let h = u f g in h 4 Quel nom plus explicite pourrait-on donner à la fonction u ci-dessus? La fonction u réalise la composition de ses deux premiers arguments : avec des notations plus mathématiques, on peut dire que u a b renvoie la fonction a b. Du coup, le résultat de l évaluation est [(x x + 1) (x 2x)] (4) = 9. EX. 19 Prévoir le type de chacune des fonctions suivantes : let g u x = u (x+1) + 1 let h u x = (u x) + 1 Le type de g est (int int) int int. En effet, la présence de l expression x + 1 montre que x est de type int, puis celle de l expression u (x + 1) + 1 montre que u est de type int int. Le type de h est ( a int) a int. En effet, l expression (u x) + 1 montre que u est une fonction dont le type de retour est int, mais ne donne aucune information sur son type d entrée, qui demeure donc indéterminé. EX. 20 Si f, g sont deux fonctions numériques (de type int int), on appelle produit tensoriel de f par g la fonction ( x, y ) f (x) g ( y ). Ecrire une fonction prod_tensoriel et prévoir quel sera son type. Si l on adopte la définition suivante : let prod_tensoriel f g x y = (f x) * (g y) alors le type de prod_tensoriel est ( a int) ( b int) int int int. Si l on veut en limiter l usage à des fonctions f et g de type int int, on peut utiliser une coercition de type (cf. exercice 6 de cette liste) : let prod_tensoriel f g (x:int) (y:int) = (f x) * (g y) Dans ce cas, le type de prod_tensoriel devient (int int) (int int) int int int.