Exercice 1 : Manipulations de listes (4 points)
Implémenter les 5 fonctions suivantes. Vous pouvez utiliser les fonctions de la librairie standard si vous le voulez.
val constant_list : int -> 'elt -> 'elt list
let res = constant_list 5 'a'
val res : char list = [ 'a'; 'a'; 'a'; 'a'; 'a']
val have_length_5 : 'elt list list -> bool
val look_up : 'key -> ('key * 'img) list -> 'img option
let res = look_up 3 [ (2,'b'); (4,'d'); (3,'c'); (1,'a')]
val res : char option = Some 'c'
val factors : 'elt list -> 'elt list list
let res = factors [1;3;2;4]
val res : int list list =
[ []; [1]; [3]; [2]; [4]; [1;3]; [3;2]; [2;4]; [1;3;2]; [3;2;4]; [1;3;2;4] ]
Solution :
let rec constant length elt =
if length <= 0 then []
else elt::constant (length-1) elt
let have_length_5 list = List.for_all (fun l -> List.length l = 5) list
let rec look_up key = function
| (k,img)::_ when k = key -> Some img
| _::tail -> look_up key tail
| [] -> None
let rec suffixes = function
| [] -> []
| _::tail as list -> list :: suffixes tail
let prefixes list = List.map List.rev (suffixes (List.rev list))
let factors list =
[] ::
List.flatten
(List.map prefixes (suffixes list))
Exercice 2 : Substitutions (6 points)
Évaluer par substitutions successives les expressions suivantes.
let rec span predicat accepted list = match list with
| head::tail when predicat head -> span predicat (head::accepted) tail
| _ -> (List.rev accepted, list)
in
let test = (fun n -> n > 5) in
span test [] [7;6;5;4;3;2;1]
let rec make f init = match f init with
| Some e -> e :: make f e
| None -> []
in let pred = function
| 0 -> None
| n -> Some (n-1)
in make pred 4
let rec collatz i = function
| 1 -> i
| n when n mod 2 = 0 -> collatz (i+1) (n/2)
| n -> collatz (i+1) (3 * n + 1)
in collatz 1 3
let rec interlace list1 list2 = match list1, list2 with
| [], l -> l
| head::tail,l -> head :: interlace l tail
in interlace [1;2;3] [4;5]
Solution :
span test [] [7;6;5;4;3;2;1]
--> span test [7] [6;5;4;3;2;1]
--> span test [6;7] [5;4;3;2;1]
--> ([7;6], [5;4;3;2;1])
make pred 4
--> 3 :: make pred 3
--> 3 :: 2 :: make pred 2
--> 3 :: 2 :: 1 :: make pred 1
--> 3 :: 2 :: 1 :: 0 :: make pred 0
--> [ 3; 2; 1; 0 ]
collatz 1 3
--> collatz 2 10
--> collatz 3 5
--> collatz 4 16
--> collatz 5 8
--> collatz 6 4
--> collatz 7 2
--> collatz 8 1
--> 8
interlace [1;2;3] [4;5]
--> 1 :: interlace [4;5] [2;3]
--> 1 :: 4 :: interlace [2;3] [5]
--> 1 :: 4 :: 2 :: interlace [5] [3]
--> 1 :: 4 :: 2 :: 5 :: interlace [3] []
--> 1 :: 4 :: 2 :: 5 :: 3 :: interlace [] []
--> [ 1; 4; 2; 5; 3 ]
Exercice 3 : Exercice de style (4 points)
La fonction suivante fut écrite n'importe comment. Réécrivez-la de façon lisible. Ensuite, réécrivez-la sans récursion en utilisant les fonctionnelles de listes.
let rec bad_function a =
match a with
| (k, []) :: z -> bad_function
z
| (k, x::y)
::z -> if k then x + bad_function
((true, y) :: z)
else bad_function ((false,y)
::z)
| _ -> 0
Solution :
let rec sum_of_ok_lists = function
| (false,_)::tail -> sum_of_ok_lists tail
| (true,int::int_list)::tail -> int + sum_of_ok_lists ((true,int_list)::tail)
| (true,[])::tail -> sum_of_ok_lists tail
| [] -> 0
let sum_of_ok_lists list =
List.filter fst list
|> List.map snd
|> List.flatten
|> List.fold_left (+) 0
Exercice 4 : 2048 (6 points)
2048 est le nom d'un jeu ayant récemment fait le buzz sur Internet. Il se présente sous la forme d'un damier de dimension 4x4 . Chaque case est vide, ou bien contient une seule tuile sur laquelle figure un entier. Le joueur choisit à chaque tour une des quatre directions (haut, bas, gauche, droite), et cela pousse au maximum toutes les tuiles dans cette direction. Les tuiles ne peuvent pas se superposer, mais lorsque deux tuiles de même valeur sont poussées l'une vers l'autre, elles fusionnent en une seule tuile de valeur double.
Il n'est pas utile de connaître le jeu pour résoudre les questions suivantes.
Nous représentons une position du jeu grâce aux types suivants :
type tile = int option
type line = tile list
type table = line list
L'exemple suivant nous servira à plusieurs reprises.
let t : table =
[ [ None; Some 2; None; None ];
[ None; Some 4; Some 8; None ];
[ None; Some 2; Some 4; Some 4 ];
[ None; Some 2; Some 4; Some 2 ]
]
La première liste représente donc la ligne du haut, de gauche à droite, et ainsi de suite.
Faire tomber une ligne
Voici le code de la fonction faisant tomber les tuiles d'une ligne vers la gauche :
let fall_line : line -> line = fun line ->
let rec loop none_tiles = function
| Some i :: Some j :: tail when i = j -> Some (i+j) :: loop (None :: none_tiles) tail
| Some i :: None :: tail -> loop (None :: none_tiles) (Some i :: tail)
| Some i :: tail -> Some i :: loop none_tiles tail
| None :: tail -> loop (None :: none_tiles) tail
| [] -> none_tiles
in loop [] line
Calculer le résultat de fall_line [ Some 4; None; Some 4; Some 8; None; Some 2 ], en procédant par substitutions successives.
Solution :
fall_line [ Some 4; None; Some 4; Some 8; None; Some 2 ]
--> loop [] [ Some 4; None; Some 4; Some 8; None; Some 2 ]
--> loop [None] [ Some 4; Some 4; Some 8; None; Some 2 ]
--> Some 8 :: loop [None; None] [ Some 8; None; Some 2 ]
--> Some 8 :: loop [None; None; None] [ Some 8; Some 2 ]
--> Some 8 :: Some 8 :: loop [None; None; None] [Some 2]
--> Some 8 :: Some 8 :: Some 2 :: loop [None; None; None] []
--> [ Some 8; Some 8; Some 2; None; None; None ]
Faire tomber les tuiles à gauche
Pour faire tomber tout le damier, il suffit de faire tomber chaque ligne indépendamment. En déduire la fonction qui prend un tableau et fait tomber les tuiles de chaque ligne vers la gauche.
val fall_leftward : table -> table
let t' = fall_leftward t
val t' : table =
[ [ Some 2; None; None; None];
[ Some 4; Some 8; None; None];
[ Some 2; Some 8; None; None];
[ Some 2; Some 4; Some 2; None] ]
Solution :
let fall_leftward table = List.map fall_line table
Faire tourner le tableau
Pour faire tomber les tuiles dans les autres directions, il suffit de savoir faire tourner le tableau de 90 degrés, pour se ramener à fall_leftward. Écrire une fonction effectuant une telle rotation (dans le sens horaire) du tableau.
Remarquez que la $k^\textrm{e}$ ligne après la rotation correspond à la $k^\textrm{e}$ colonne avant la rotation, en sens inverse.
Pour cette question, vous avez exceptionnellement le droit d'utiliser List.hd pour récupérer le premier élément d'une liste, et List.tl pour récupérer la queue d'une liste.
val clockwise_rotation : table -> table
let t'' = clockwise_rotation t;;
val t'' : table =
[ [ None; None; None; None ];
[ Some 2; Some 2; Some 4; Some 2 ];
[ Some 4; Some 4; Some 8; None ];
[ Some 2; Some 4; None; None ] ]
Solution :
let rec clockwise_rotation : table -> table = fun table ->
if List.exists (fun l -> l = []) table then []
else
List.rev (List.map List.hd table) :: clockwise_rotation (List.map List.tl table)
Faire tomber les tuiles dans les autres directions
En déduire les fonctions pour faire tomber les tuiles dans les autres directions.
val fall_downward : table -> table
val fall_rightward : table -> table
val fall_upward : table -> table
Solution :
let (@@) f g x = f (g x)
let half_rotation = clockwise_rotation @@ clockwise_rotation
let anti_clockwise_rotation = clockwise_rotation @@ half_rotation
let fall_upward = clockwise_rotation @@ fall_leftward @@ anti_clockwise_rotation
let fall_rightward = half_rotation @@ fall_leftward @@ half_rotation
let fall_downward = anti_clockwise_rotation @@ fall_leftward @@ clockwise_rotation
Question bonus : Rajouter une tuile
Seulement si vous avez fini toutes les autres questions !
Après chaque tour du joueur, une tuile est ajoutée dans une position vide choisie aléatoirement. La nouvelle tuile aura la valeur $2$ avec probabilité
$0.9$, et $4$ avec probabilité $0.1$. Coder une fonction permettant l'ajout d'une tuile selon ce procédé.
Solution :
let on_none value = function
| None -> [value]
| Some _ -> []
let rec on_list f = function
| head::tail -> List.map (fun im -> im::tail) (f head) @ List.map (fun l -> head::l) (on_list f tail)
| [] -> []
let fill_an_empty_square : table -> table option = fun table ->
let value = if Random.int 10 = 0 then 4 else 2 in
match on_list (on_list (on_none (Some value))) table with
| [] -> None
| choices -> Some (List.nth choices (Random.int (List.length choices)))
Éléments de langage
Fonctions utiles de la librairie standard :
val max : 'a -> 'a -> 'a
val sqrt : float -> float
val ( ** ) : float -> float -> float
val (@) : 'a list -> 'a list -> 'a list
type 'elt option =
| Some of 'elt
| None
Fonctions utiles du module List :
val hd : 'elt list -> 'elt
val tl : 'elt list -> 'elt list
val length : 'a list -> int
val nth : 'a list -> int -> 'a
val rev : 'a list -> 'a list
val flatten : 'a list list -> 'a list
val map : ('a -> 'b) -> 'a list -> 'b list
val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
val fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b
val for_all : ('a -> bool) -> 'a list -> bool
val exists : ('a -> bool) -> 'a list -> bool
val mem : 'a -> 'a list -> bool
val filter : ('a -> bool) -> 'a list -> 'a list
val fast_sort : ('a -> 'a -> int) -> 'a list -> 'a list
Exemples de syntaxe :
type ratio = int * int
let rec euclid a b =
if b = 0 then a
else euclid b (a mod b)
let pgcd a b = euclid (abs a) (abs b)
let ratio p q =
let d = pgcd p q in
if q < 0 then (-p / d, q / d)
else (p/d, q/d)
let (+/) (a,b) (c,d) = ratio (a*d + b*c) (b*d)
let (++) (a,b) (c,d) = ratio (a+c) (b+d)
module Queue =
struct
type 'a t =
Queue of ('a list) * ('a list)
exception Empty
let empty = Queue ([],[])
let is_empty = function
| Queue ([],[]) -> true
| _ -> false
let queue = function
| Queue ([],l) -> Queue(List.rev l,[])
| x -> x
let snoc (Queue (l1,l2)) ele =
queue (Queue (l1,ele::l2))
let head = function
| Queue ([],_) -> raise Empty
| Queue (l,_) -> List.hd l
let tail = function
| Queue ([],_) -> raise Empty
| Queue (l1,l2) -> queue (Queue (List.tl l1,l2))
end