CORRECTION INF121 : Contrôle continu (2h00) Mars 2012 Documents autorisés : Uniquement une feuille A4 recto-verso de notes personnelles manuscrites. Notes : Vous répondrez aux exercices et au problème dans l ordre qui vous convient. Le total des points de chaque question correspond au nombres de minutes nécessaires pour résoudre la question. Les questions difficiles sont marquées par des étoiles (**). Vous pouvez admettre le résultat d une question, et l utiliser dans la suite. Rappel : Définir une fonction signifie : 1. Profil 2. Sémantique 3. Exemples 1. Si elle est récursive : Terminaison 2. Code OCaml Exercice 1 : Typage et valeur d une expression (15 points) Rappel : fst (a,b) = a et snd (a,b) = b Pour chaque phrase OCaml, indiquez le type du résultat et sa valeur. Si une expression est mal typée, indiquez le et justifiez. On donnera également le type des fonctions f et g. 1. (3 points) snd (2,true) && false - : bool = false 2. (3 points) let a = (1,2) in fst (a,2) - : int * int = (1, 2) 3. (3 points) fst (3,2 mod 2 = 0) + 1 - : int = 4 4. (3 points) let f (x : int) : int = (x mod 10, x / 10) in f (snd (f 123)) let f (x : int) : int = (x mod 10, x / 10) in f (snd (f 123));; ^^^^^^^^^^^^^^^^^^ Error: This expression has type int * int but an expression was expected of type int 5. (3 points) let g (x : float) : float float = (x,x) in g (g 2.) let g (x : float) : float * float = (x,x) in g (g 2.);; ^^^^^^ Error: This expression has type float * float but an expression was expected of type float 1
Exercice 2 : Fonctions récursives (35 points) 1. (10 points) Définir une fonction récursive qui prend deux entiers a et b et calcule la somme de tous les entiers entre a et b, a et b compris, ainsi la somme entre a et a vaut a. Nous supposons que a b. (a) Profil : somme : Z Z Z (b) Sémantique : calcule la somme de tous les entiers entre a et b. (c) Exemples : somme 0 10 = 55 somme 10 20 = 165 somme ( 4) 3 = 4 : somme a a = a somme a b = a + somme (a+1) b Terminaison : Nous exhibons une mesure m sur les paramètres de la fonction récursive. Par exemple m(a, b) = b a Nous montrons que cette mesure décroit à chaque appel récursif. let rec somme (a:int) (b:int):int = if a = b then a else a + somme (a+1) b;; m(a, b) = b a > m(a + 1, b) = b (a + 1) = b a 1 2. (6 points) (*) Donner la réalisation d une autre solution qui effectuera la récursion sur l autre argument. let rec sommedown (a:int) (b:int):int = if a = b then a else b + sommedown (a) (b-1);; 3. (7 points) Écrire une fonction non récursive qui effectue le même calcul, en utilisant uniquement des additions, soustractions, multiplications et divisions. Nous supposons que a b. let sommesimple (a:int) (b:int):int = Ou let sommesimplebis (a:int) (b:int):int = (b*(b+1) - a*(a-1))/2;; a*(b-a+1) + (b-a)*(b-a+1)/2;; 4. Complexité : (2 points) Donner le nombre total d appels récursifs faits dans la fonction écrite à la question 1. b-a appels récursifs et un appel non récursif (2 points) Compter le nombre d opérations effectuées dans la fonction écrite à la question 3. 2 multiplications + 1 division + 1 addition + 2 soustractions (4 points) Considérons que les additions, soustractions, multiplications et divisions coûtent le même temps. Quel est l algorithme qui est le plus rapide? Nous avons sommesimple : 6 opérations et somme : (b-a)*2 (ou (b-a)*3+1 en comptant = comme une soustraction) opérations. Donc si (b-a) < 3 (ou (b-a) < 5/3), c est-à-dire si la somme porte sur plus de 4 (ou 3) nombres alors l algorithme somme est plus rapide, sinon c est l algorithme sommesimple. 2
(4 points) Considérons que chaque addition, soustraction prend t fois moins de temps que chaque multiplication ou division. Déterminer la valeur de t pour que l algorithme 1 soit plus rapide moins que celui de la fonction demandée à la question 3. Soit m le nombre de * ou / et p le nombre de + ou - dans la formule simple. Nous avons sommesimple : m+pt et somme : (b-a)*2t (ou (b-a) +1 en comptant = comme une soustraction). somme est plus rapide si 2 (b-a) < m+pt (ou 3 (b-a) + 1 < m+pt) d où pt > 2 (b-a) -m et t > (2 (b-a) - m) / p) (ou pt > 3 (b-a) - (m+1) et t > (3 (b-a) - (m+1)) / p) Pour la formule (y (y+1) - x (x-1))/2 on a m=p=3 : t > 2/3 (b-a) -1 (ou t > b-a -4/3) Exercise 3 : Jouons au Tarot (75 points) Un jeu de tarot comporte 78 cartes à jouer : Cinquante-six cartes réparties en quatorze cartes des quatre couleurs traditionnelles : pique, cœur, carreau et trèfle. La différence avec un jeu traditionnel de 52 cartes est le cavalier, figure s intercalant entre la dame et le valet. Dans l ordre décroissant de force et de valeur, on trouve donc : + les honneurs : Roi, Dame, Cavalier et Valet + les petites : 10, 9, 8, 7, 6, 5, 4, 3, 2, 1. Le 1 est la plus petite carte du paquet contrairement à ce qui se pratique dans de nombreux jeux où l As est plus fort que le Roi. Vingt et une cartes portant un numéro : ce sont les atouts (ou tarots) qui ont priorité sur les couleurs. Le numéro indique la force de chaque atout, du plus fort, le 21, au plus faible, le 1. L «excuse», une carte marquée d une étoile et représentant un joueur de mandoline. Il s agit d une sorte de joker. a) Modélisation (7 points) Nous définissons les types suivants : type dos = Petite of int Valet Cavalier Dame Roi;; type couleur = Trefle Pique Carreau Coeur ;; type carte = Atout of int Excuse Normale of dos couleur ;; type carteliste = Nil Cons of carte carteliste;; Nous définissons le type main qui modélise l ensemble des cartes en possession d un joueur, par type main = carteliste. Nous définissons le type pli qui représente l ensemble des cartes gagnées à chaque tour de jeu par type pli = carteliste. Notre modélisation doit être valable pour 3, 4 ou 5 joueurs. 1. (7 points) Représentez en OCaml (dans votre type) la main contenant les cartes suivantes : «valet de pique», «9 d atout», «excuse», «roi de carreau», «9 de coeur», «21 d atout». let main = Cons( Normale(Valet,Pique), Cons( Atout(9), 3
Cons( Excuse, Cons( Normale(Roi,Carreau), Cons( Normale(Petite(9),Coeur), Cons( Atout(21),Nil))))));; b) Points (40 points) À la fin de la partie, on compte les points contenus dans les plis (i.e., les cartes prises par un groupe de joueur). On omet de nombreuses règles subtiles du tarot ici, pour se concentrer sur l essentiel. Chaque carte vaut un nombre précis de point, donné par la règle suivante : Atout 1, Excuse et Atout 21 : 4.5 points Roi : 4.5 points Dame : 3.5 points Cavalier : 2.5 points Valet : 1.5 points Toute autre carte : 0.5 points 1. (2 points) Combien de points compte la main donnée à la question a)1)? 16 2. (2 points) Combien de points compte le pli qui est composé des cartes valet de pique, douze d atout, excuse et roi de carreau? 11 3. (6 points) Définir une fonction valeur: carte float, qui à une carte associe son nombre de points. (a) Profil : valeur: carte float (b) Sémantique : A une carte associe son nombre de points. (c) Exemples : valeur Excuse = 4.5 valeur Roi = 4.5 (a) Code OCaml let valeur (c:carte): float = match c with Excuse -> 4.5 Atout (1) -> 4.5 Atout (21) -> 4.5 Normale (Roi,_) -> 4.5 Normale (Dame,_) -> 3.5 Normale (Cavalier,_) -> 2.5 Normale (Valet,_) -> 1.5 _ -> 0.5 ;; 4. (7 points) Définir la fonction compte: pli float, qui compte les points contenus dans un pli. (a) Profil : compte: pli float (b) Sémantique : compte les points contenus dans un pli. (c) Exemples : compte(main) = 16 : compte(nil) = 0. compte(cons(t, q)) = valeur(t) +.compte(q) let rec compte (l:pli):float = match l with Nil -> 0. Cons(t,q) -> valeur t +. compte q ;; 4
5. (10 points) Nous considérons le type suivant : type pliliste = Nil Cons of pli pliliste;; Définir une fonction veriftotal: pliliste bool, qui vérifie que le total des points de la liste des plis passés en paramètre est bien 91 points. Vous pouvez réutiliser la fonction compte de la question précédente et toute autre fonction intermédiaire que vous jugerez nécessaire. (a) Profil : veriftotal: pliliste bool (b) Sémantique : vérifie que le total des points de la liste des plis passés en paramètre est bien 91 points. Pour cela nous introduisons une fonction récursive qui calcule la somme des points d une liste et nous testons si le résultat est bien 91. (c) Exemples : veriftotal(main) = false sommepointspli(n il) = 0. sommepointspli(cons(t, q)) = compte(t) +.sommepointspli(q) let veriftotal (p: pli liste) : bool = let rec sommepointspli (l: pli liste) : float = Nil -> 0. Cons(t,q) -> compte t +. sommepointspli q in sommepointspli p = 91. ;; match l with 6. (6 points) Définir une fonction nbatout: main int, qui compte le nombre d atouts contenus dans une main passée en paramètre. (a) Profil : nbatout: main int (b) Sémantique : compte le nombre d atouts contenus dans une main passée en paramètre. (c) Exemples : nbatout(main) = 3 nbatout(nil) = 0. nbatout(cons(t, q)) = 1 + nbatout(q) let rec nbatout (l:main):int = match l with Nil -> 0 Cons(t,q) -> match t with Atout(_) -> 1+ nbatout q _ -> nbatout q;; 7. (7 points) Si un joueur possède plus de 10 atouts dans sa main intiale, il peut obtenir des points bonus, nous dirons qu il a une poignée. Plus précisément une simple poignée correspond à au moins 10 Atouts et au plus 12 Atouts et une prime de 20 points, une double poignée correspond à au moins 13 Atouts et au plus 14 et une prime de 30 points et une triple poignée correspond à au moins 15 Atouts et à une prime de 40 points. Définir la fonction pointbonus: main float, qui détermine en fonction de la main d un joueur le nombre de points bonus attribués en fonction de son nombre d atouts. Vous devrez réutiliser la fonction nbatout de la question précédente. 5
1. Profil : pointbonus: main float 2. Sémantique : détermine en fonction de la main d un joueur le nombre de points bonus attribués en fonction de son nombre d atouts 3. Exemples : pointbonus(main) = 0 1. Code OCaml let pointbonus (m: main) : float = let nba = nbatout m in if nba >= 15 then 40. else if nba >=13 then 30. else if nba >= 10 then 20. else 0.;; c) Vérification (28 points) Afin de détecter les éventuels tricheurs nous allons vérifier que les cartes ne sont jouées qu une seule fois. 1. (8 points) Définir une fonction appart : carte pli bool qui détermine si une carte donnée appartient à un pli. (a) Profil : appart : carte pli bool (b) Sémantique : détermine si une carte donnée appartient à un pli. (c) Exemples : appartexcusemain = true : appart(c, Nil) = false appart(c, Cons(t, q)) = true si c = t sinon appartcq let rec appart (c:carte) (p:pli) : bool = match p with Nil -> false Cons(t,q) -> if t = c then true else appart c q;; 2. (8 points) Définir une fonction oter : carte main main qui supprime toute les occurrences d une carte donnée dans une main. (a) Profil : oter : carte main main (b) Sémantique : supprime toute les occurrences d une carte donnée dans une main. (c) Exemples : oter Excuse main = Cons( Normale(Valet,Pique), Cons( Atout(9), Cons( Normale(Roi,Carreau), Cons( Normale(Petite(9),Coeur), Cons( Atout(21),Nil)))));; otercnil = Nil oterccons(x, r) = r si c = x sinon Cons(x, (oter(c)(r)) 6
let rec oter (c:carte) (p:pli) : pli = match p with Nil -> Nil Cons(t,q) -> if t = c then oter c q else Cons(t,oter c q);; 3. (12 points) Définir une fonction unicite : main bool qui détermine si une main ne contient pas deux fois la même carte. Vous pouvez utiliser la fonction appart. (a) Profil : unicite : main bool (b) Sémantique : détermine si une main ne contient pas deux fois la même carte (c) Exemples : unicite(main) = true unicite(nil) = true unicite(cons(t, q)) = false si t apprtient a q sinon unicite(q) let rec unicite2 (p:pli) : bool = match p with Nil -> true Cons(t,q) -> if appart t q then false else unicite2 d) Jeu de la carte (***) BONUS (20 points) Définir une fonction qui détermine étant donné un pli d au moins une carte, la carte qui l emporte. Vous pouvez d abord définir des fonctions intermédiaires avant de répondre à cette question. Nous indiquons les règles élémentaires du tarot permettant de déterminer qui emporte un pli. Si la première carte est un atout, c est l atout le plus fort présent dans le pli qui l emporte. Si la première carte détermine la couleur jouée, alors c est l atout le plus fort présent dans le pli qui l emporte, sinon s il n y a pas d atout, c est la plus forte carte de la couleur jouée qui l emporte. Si la première carte d un pli est l excuse, c est la carte suivante qui détermine la couleur jouée. Rappel Soit un type défini par type alphabet = A B C;; alors A>B>C. Nous donnons uniquement le code OCaml let rec quigagne (p:pli) : carte = let rec plusgrand (x:int) (p:pli): int = match p with Nil -> x Cons (Atout(y),q) -> if y>x then plusgrand y q else plusgrand x q Cons (t,q) -> plusgrand x q in let rec contientatout (p:pli) = match p with Nil -> false Cons (Atout(y),q) -> true Cons (t,q) -> contientatout q in let rec plusgrandcouleur (d:dos) (p:pli) (c:couleur):carte = match p with Nil -> Normale(d,c) Cons(Normale(ddo,coul),q) -> if (sup ddo d)&&(coul=c) q;; 7
then plusgrandcouleur ddo q c else plusgrandcouleur d q c Cons (t,q) -> plusgrandcouleur d q c in match p with Nil -> failwith "pli vide" Cons(first, reste) -> match first with Excuse -> quigagne reste Atout(x) -> Atout(plusgrand x reste) Normale(d,c) -> if contientatout(reste) then Atout(plusgrand 0 reste) else plusgrandcouleur d reste c ;; 8