Vous êtes libres d'utiliser tout éditeur de texte de votre choix, à partir du moment où vous le maîtrisez et êtes à même de développer efficacement. Si vous souhaitez utiliser l'éditeur Emacs, voici quelques indications. On ouvre le fichier td1.ml en lançant la commande emacs td1.ml ou, si Emacs est déjà lancé, avec la commande Ctrl-x Ctrl-f. Vous pouvez compiler directement depuis Emacs avec la commande Meta-x compile (Meta s'obtient avec la touche Alt ou Esc, au choix). Vous pouvez relancer la même commande avec Meta-x recompile. Si le compilateur OCaml signale une erreur, vous pouvez vous placer directement sur la position correspondante avec Meta-x next-error. Vous pouvez simplifier les raccourcis clavier de ces trois commandes en insérant les lignes suivantes dans votre fichier ~/.emacs :
(global-set-key [f5] 'compile) (global-set-key [f6] 'recompile) (global-set-key [f7] 'next-error)C'est là une façon très efficace de travailler. (Et vous pourrez utiliser ces raccourcis pour toute autre chose que la programmation OCaml.)
Ce TD comporte trois exercices indépendants. Ceux d'entre vous qui se sentent à l'aise avec Caml mais souhaitent tout de même faire l'exercice 1 sur les tris peuvent attaquer directement à la question tri par tas.
Écrire une fonction insertion_sort : int list -> int list qui réalise le tri par insertion d'une liste d'entiers. On rappelle que cela consiste à extraire le premier élément de la liste, à trier récursivement le reste de la liste, puis à insérer l'élément isolé (avec la fonction insertion).
On pourra tester avec le code suivant :
let rec print fmt = function | [] -> () | [x] -> Format.fprintf fmt "%d" x | x :: l -> Format.fprintf fmt "%d, %a" x print l let rec is_sorted = function | [] | [_] -> true | x :: (y :: _ as l) -> x <= y && is_sorted l let check l = let r = insertion_sort l in let ok = is_sorted r in Format.printf "[%a] => [%a]: %s@." print l print r (if ok then "OK" else "FAILED"); if not ok then exit 1 let () = check [1; 2; 3]; check [3; 2; 1]; check []; check [1]; check [2; 1; 1]
Écrire une fonction fusion : int list -> int list -> int list qui prend en arguments deux listes supposées triées en ordre croissant et les fusionne en une unique liste triée.
En utilisant les deux fonctions précédentes, écrire une fonction mergesort : int list -> int list réalisant le tri fusion. On rappelle que le principe consiste à couper la liste en deux, à trier récursivement chacune des deux listes obtenues, puis à fusion les deux listes triées.
Tester comme pour le tri par insertion.
Écrire une fonction quicksort : int list -> int list réalisant le tri rapide (quicksort). On rappelle que le principe consiste à isoler un élément particulier de la liste (ici le premier), à partionner les éléments restants en utilisant la fonction partition ci-dessus, à trier récursivement les deux listes obtenues, et enfin à recoller les morceaux. Pour cette dernière étape on pourra utiliser la fonction de bibliothèque List.append qui effectue la concaténation de deux listes (elle se note également @ de manière infixe).
Tester.
1 / \ 2 5 / \ 4 2 \ 6
Le but de cette question est de réaliser les deux opérations élémentaires sur la structure de tas que sont l'insertion d'un nouvel élément et l'extraction du plus petit élément (c'est-à-dire la racine). On se donne le type heap suivant pour représenter les tas :
type heap = Null | Fork of int * heap * heapOn commence par une opération plus complexe, merge : heap -> heap -> heap, qui réalise la fusion de deux tas a et b. Le principe est le suivant. Si a ou b est Null, le résultat est immédiat. Sinon, on compare la racine de a et celle de b ; supposons que la racine de a soit la plus petite (sinon, on échange les rôles de a et b). Le résultat est alors un tas dont la racine est celle de a, dont le sous-arbre gauche est le sous-arbre droit de a, et dont le sous-arbre droit est obtenu en fusionnant récursivement le sous-arbre gauche de a et l'arbre b. Écrire la fonction merge.
À l'aide de la fonction merge, écrire une fonction add : int -> heap -> heap qui insère un nouvel élément dans un tas. (On pourra se servir du tas Fork (x, Null, Null) qui contient uniquement x.)
De même, utiliser la fonction merge pour écrire une fonction extract_min : heap -> int * heap qui extrait le plus petit élément d'un tas (sa racine) et renvoie le tas constitué par tous les autres éléments. Lorsque le tas est vide, on pourra lever l'exception Empty_heap ainsi déclarée :
exception Empty_heap
Note : ces tas s'appellent des skew heaps et sont à la fois persistants, élégants et très efficaces.
On pourra tester avec le code suivant :
let rec print_heap fmt = function | Null -> Format.fprintf fmt "Null" | Fork (x, a, b) -> Format.fprintf fmt "Fork (@[%d,@ %a,@ %a@])" x print_heap a print_heap b let rec is_heap_rec min = function | Null -> true | Fork (x, l, r) -> min <= x && is_heap_rec x l && is_heap_rec x r let is_heap = is_heap_rec min_int let check_heap h = let ok = is_heap h in Format.printf "%a: %s@." print_heap h (if ok then "OK" else "FAILED"); if not ok then exit 1 let () = let h1 = add 2 (add 1 (add 3 Null)) in check_heap h1; let h2 = add 4 (add 0 (add 1 Null)) in check_heap h2; let h = merge h1 h2 in check_heap h; let m, h = extract_min h in assert (m = 0); check_heap h
Pour cela, commencer par écrire une fonction heap_of_list : int list -> heap qui construit un tas avec tous les éléments d'une liste.
Écrire ensuite la fonction inverse list_of_heap : heap -> int list qui construit une liste triée avec tous les éléments d'un tas.
Enfin, combiner les deux fonctions précédentes en une fonction heapsort : int list -> int list réalisant le tri par tas.
Tester comme pour les autres tris.
sort : ('a -> 'a -> bool) -> 'a list -> 'a listoù la fonction passée en argument correspond à la relation « inférieur ou égal à ».
On pourra tester ainsi :
let rec is_sorted le = function | [] | [_] -> true | x :: (y :: _ as l) -> le x y && is_sorted le l let check le l = let r = sort le l in let ok = is_sorted le r in Format.printf "[%a] => [%a]: %s@." print l print r (if ok then "OK" else "FAILED"); if not ok then exit 1 let () = check (<=) [1; 2; 3]; check (<=) [3; 2; 1]; check (<=) []; check (<=) [1]; check (<=) [2; 1; 1]; check (>=) [1; 2; 3]; check (>=) [3; 2; 1]; check (>=) []; check (>=) [1]; check (>=) [2; 1; 1]
module Sort(X : sig type t val le : t -> t -> bool end) : sig val sort : X.t list -> X.t list end = struct let sort l = ... endOn pourra tester avec des entiers, ainsi :
module S = Sort(struct type t = int let le = (<=) end);; let rec print fmt = function | [] -> () | [x] -> Format.fprintf fmt "%d" x | x :: l -> Format.fprintf fmt "%d, %a" x print l let rec is_sorted le = function | [] | [_] -> true | x :: (y :: _ as l) -> le x y && is_sorted le l let check l = let r = S.sort l in let ok = is_sorted (<=) r in Format.printf "[%a] => [%a]: %s@." print l print r (if ok then "OK" else "FAILED"); if not ok then exit 1 let () = check [1; 2; 3]; check [3; 2; 1]; check []; check [1]; check [2; 1; 1]
Par exemple, en tapant successivement les touches 2, 6, 6, 5, 6, 8 et 7 vous obtenez le mot bonjour. Il est possible qu'une suite de touches corresponde à plusieurs mots. Ainsi, la suite 5, 6, 4 et 3 correspond aux mots joie et loge et la suite 2, 5, 3 et 3 à clef et bled.
Le but de cet exercice est de programmer une solution efficace pour la recherche des mots du dictionnaire qui correspondent à une séquence de touches. Pour cela, nous allons représenter le dictionnaire par un arbre digital (en anglais trie) c'est-à-dire un arbre où chaque branche est étiquetée par le numéro d'une touche du téléphone et chaque noeud par les mots du dictionnaire correspondant à la séquence des touches menant de la racine de l'arbre à ce noeud.
Par exemple, l'arbre que l'on obtient pour le petit dictionnaire d'informatique composé des mots {hd, if, in, get, to, void, unit} est le suivant :
{} / \ 4/ \8 / \ {} {} / \ \ 6/ \3 \6 / \ \ {in} {if,hd} {to} | | |8 |4 | | {get} { } / \ 3/ \8 / \ {void} {unit}Pour représenter un tel arbre digital, on se donne le type Caml suivant :
type trie = { words : string list ; branches : (int * trie) list }i.e. chaque noeud de l'arbre est un enregistrement où le champ words contient la liste des mots correspondant à ce noeud et où le champ branches est une liste de paires associant des arbres digitaux à des entiers (on appelle précisément cela une liste d'association).
change_assoc : 'a -> 'b -> ('a * 'b) list -> ('a * 'b) listqui modifie (ou étend) une liste d'association.
let dict = [ "lex"; "key"; "void" ; "caml" ; "unix" ; "for" ; "while" ; "done" ; "let" ; "fold"; "val"; "exn" ; "rec" ; "else" ; "then" ; "type" ; "try" ; "with" ; "to"; "find" ; "do" ; "in" ; "if" ; "hd" ; "tl" ; "iter" ; "map" ; "get"; "copy" ; "and"; "as" ; "begin" ; "class" ; "downto" ; "end" ; "exception" ; "false" ; "fun" ; "function" ; "match" ; "mod" ; "mutable" ; "open" ; "or" ; "true" ; "when" ; "load" ; "mem" ; "length" ; "bash" ; "unit" ; "site"; "php"; "sql"; "ssh"; "spam"; "su"; "qt"; "root"; "bsd"; "boot"; "caml"; "bash"; "ocaml"; "kde"; "gtk" ; "gcc" ](On pourra utiliser pour cela la fonction de bibliothèque List.fold_left.)
Tester avec le code suivant :
let () = assert (find t [2;2;6;5] = ["caml"]) let () = assert (let l = find t [4;3] in l = ["hd"; "if"] || l = ["if"; "hd"]) let () = assert (find t [4;3;8] = ["get"]) let () = assert (find t [8;6;4;3] = ["void"]) let () = assert (find t [8;6;4;8] = ["unit"])
Tester avec le code suivant :
let t = remove_word t "caml" let () = assert (find t [2;2;6;5] = []) let () = assert (let l = find t [4;3] in l = ["hd"; "if"] || l = ["if"; "hd"]) let () = assert (find t [4;3;8] = ["get"])
% ocamlc -o quad graphics.cma quad.mlou encore avec le compilateur natif :
% ocamlopt -o quad graphics.cmxa quad.mlCeci produira l'exécutable quad. Cet exécutable dessine des images, puis s'arrête. L'affichage des images est contrôlé à l'aide des touches du clavier. À tout moment la touche « q » (avec le curseur de la souris dans la fenêtre graphique) permet de passer à la question suivante.
Selon ce schéma, les images sont représentées par des arbres du type
suivant :
type couleur = Blanc | Noir type arbre = Feuille of couleur | Noeud of arbre * arbre * arbre * arbreOn manipulera aussi des images bitmap, représentées par des matrices de couleurs que l'on supposera toujours de côté 2n par la suite.
type image = couleur array array (* matrice de taille 2n *)
(Si vous obtenez une isométrie quelconque de ce dessin,
tranquilisez-vous et continuez.)
(Il convient ici de bien regarder la première itération pour
comprendre le processus.)
Le code fourni permet d'avancer dans les itérations avec la touche n et de reculer avec la touche p. On sort du test en appuyant sur la touche q.
code(Feuille Blanc) | = | 00 |
code(Feuille Noir) | = | 01 |
code(Noeud (a1,a2,a3,a4)) | = | 1 code(a1) code(a2) code(a3) code(a4) |
Pour simplifier on va procéder en deux temps : d'abord le codage (et
décodage) de
l'arbre en liste de bits ; puis l'écriture (et lecture) des bits dans
un fichier.
type bit = Zero | UnÉcrire une fonction arbre_vers_liste de type arbre -> bit list qui transforme un arbre en une liste de 0 et de 1 selon le codage ci-dessus. Cette fonction ressemble furieusement à l'affichage d'un arbre sous forme préfixe.