1.1- TP - Introduction à OCaml
Ce TP a pour but de découvrir le langage OCaml, ainsi que quelques principes très courants en option info.
Avant toute chose
Attention, quelques consignes sur le code !
- Les noms des variables doivent être explicites, en particulier lorsque leur rôle n'est pas évident. On tolère
let sa décompositionx::qpour une liste,fpour une formule,ipour un indice... Mais restez cohérents. - Votre code doit être commenté, et les types des arguments ainsi que le type de retour des fonctions doit être précisé. C'est d'autant plus vrai pour les fonctions auxiliaires.
- Vous devez testez chacune des fonction que vous écrivez. Vous disposez d'une variable
formule_testà cet effet.
I. Préliminaires
Syntaxe OCaml : généralités
- Les commentaires en OCaml doivent être encadrés par
(* ... *) - Les retours à la ligne et l'indentation n'ont pas d'importance (enfin, uniquement la lisibilité)
- les noms de variables ne peuvent pas commencer par une majuscule
Syntaxe Ocaml : les types
OCaml est un langage fortement typé. Cela veut dire que toutes les variables, fonctions, arguments de fonction doivent avoir un type bien défini.
Entre autre, on n'a pas de type simple "liste", mais on parle de "liste d'entiers" : il ne peux y avoir qu'un seul type d'éléments dans la liste.
Syntaxe OCaml : fonctions et variables
- Les variables globales se déclarent avec
let nom_de_variable: type = (*valeur*);; - Les fonctions se déclarent avec
let nom_de_la_fonction (arg1: type1) (arg2: type2): type_de_retour =
(*corps de la fonction*)
;;Question 1
Executez les cellules suivantes, et tentez de comprendre le résultat.
Loading editor...
let n: int = 3;;
Loading editor...
let k: int = 5;;
Loading editor...
let pi = 3.1415926535;;
La ligne val n : int = 3 signifie que une variable "n", de type "int", a été déclarée avec la valeur "3".
Loading editor...
n + k
Loading editor...
pi + pi
Loading editor...
pi +. pi
Loading editor...
n + pi
Loading editor...
La ligne - : int = 8 signifie que la ligne s'évalue en un entier, de valeur "8".
L'addition en OCaml permet soit d'additionner des entiers (avec +), soit des flottants (avec +.), mais pas un mélange des deux !
Loading editor...
let add (x: int) (y: int): int = x + y;;
Loading editor...
add n k
Loading editor...
add pi pi
La ligne val add : int -> int -> int = <fun> signifie que :
- on a une variable
add - cette variable est de type
int -> int -> int, ce qui veut dire qu'elle prend deux entiers en argument et renvoie un entier. Le type après la dernière flèche est le type de retour. - enfin, le
= <fun>signifie que notre variableaddcontient une fonction.
Loading editor...
let a = (1, 2);; let b = (4, 5);;
Loading editor...
let add2 v1 v2 =
let (a1, b1) = v1 in
let (a2, b2) = v2 in
(a1 + a2, b1 + b2)
;;Loading editor...
add2 a b
Les couples en OCaml sont de type a * b, avec a le type du 1er élément et b celui du 2ème.
On ne peut pas accéder au 1er et 2ème élément avec des v[0], comme en python, et le plus simple est de séparer le couple en deux avec de nouvelles variables.
Syntaxe OCaml : Les expressions
Dans le corps d'une fonction, ou bien d'une déclaration de variable, on a une expression. Les expression peuvent être :
- Des variables, des constantes, des opérations dessus :
x + 8;; - Des déclaration de variables locales ou de fonctions auxiliaires, déclarées avec
let [nom] = ... in - Des conditions :
if [condition] then [expresssion 1] else [expression 2]
Question 2
Que font les cellules suivantes ? Vous pouvez les exécuter sur des exemples si besoin.
Ecrivez des équivalents en langage Python, dans la cellule Python qui suit les cellules OCaml (l'étiquette en haut à gauche de chaque cellule indique son langage).
Loading editor...
let f (x: int) (y: int): int =
let sum = x + y in
let dif = x - y in
sum * dif
;;Loading editor...
let abs x =
if x < 0 then -x else x
;;Loading editor...
let vaut_zero (x: int): bool =
if x = 0 (*attention, c'est un simple = en ocaml*)
then true (*true et false ne prennent pas de majuscule*)
else false
;;Loading editor...
# vos équivalents en Python
Question 3
Traduisez les fonctions python suivantes en OCaml.
Loading editor...
def est_pair(x):
return x % 2 == 0
est_pair(6)x % 2 en OCaml s'écrit x mod 2.
Loading editor...
Loading editor...
def produit_scalaire(v1, v2):
(a, b) = v1
(c, d) = v2
return a*c + b*d
produit_scalaire((1, 2), (3, 4))Loading editor...
II. Formules et notation préfixe
1. Définitions
Une formule est définie inductivement pour
- soit une variable
- soit, si
est une formule, qui signifie "moins e" - soit, si
et sont des formules, pour l'addition pour la multiplication
Par exemple,
On appelle valuation des variables d'une formule une fonction
La valeur d'une formule, pour une valuation de ses variables, est le résultat de l'opération usuelle sur la formule, dans laquelle on a remplacé chaque variable par sa valeur.
Une formule sous notation préfixe (ou notation polonaise inversée) est une formule où, dans le cas des opérateurs binaires, on écrit
Avantage
Avec la notation préfixe, il n'est plus nécéssaire d'utiliser des parenthèses ! Essayez de comprendre pourquoi.
Question 4
Transformez les formules suivantes en notation préfixe, en des formules en notation usuelle :
2. Formules en OCaml
On va dans un premier temps représenter en OCaml les formules en notation préfixe. Pour cela, on utilise un type de liste.
Syntaxe OCaml : les listes
Les listes en OCaml ne contiennent qu'un seul type de données, et le type en question se note [type] list.
On peut donner une liste explicite avec la syntaxe [1; 2; 3; 4; 5] (attention aux point-virgules !).
On a les opérations suivantes sur les listes l1 et l2 et un élément x :
x::l1est la listel1à laquelle on a ajoutéxau débutl1 @ l2est la concaténation del1etl2
À noter qu'on n'a pas d'opérateur pour accéder au ième élément d'une liste !
Loading editor...
type formule_prefixe = char list;;
Dans une formule, le caractère * représente le + représente le - représente le
Par exemple, la formule ['+'; '-'; '*'; 'a'; '-'; 'b'; '*'; '-'; 'a'; 'b']
Loading editor...
let formule_test_prefixe = ['+'; '-'; '*'; 'a'; '-'; 'b'; '*'; '-'; 'a'; 'b'];;
Rappel
Si vous n'avez pas lu l'encadré en haut du TP, c'est le moment.
Question 5
Compléter les fonctions suivantes, qui appliquent des opérations à des formules. La négation est déjà complétée à titre d'exemple.
Loading editor...
(*Renvoie la négation d'une formule*) let neg (f: formule_prefixe): formule_prefixe = '-'::f
Loading editor...
(*Renvoie une formule consituée d'une seule variable*) let var (v: char): formule_prefixe = (*à compléter*)
Loading editor...
(*Renvoie la multiplication de deux formules*) let mul (f1: formule_prefixe) (f2: formule_prefixe): formule_prefixe = (*à compléter*)
Loading editor...
(*Renvoie l'addition de deux formules*) let add (f1: formule_prefixe) (f2: formule_prefixe): formule_prefixe = (*à compléter*)
Question 6
À l'aide des fonctions ci-dessus (et uniquement elles), réécrivez la formule formule_test_prefixe.
Loading editor...
Syntaxe OCaml : fonctions récursives
En OCaml, on peut écrire des fonctions récursives avec le mot-clé let rec. De plus, on peut faire du pattern matching sur les listes, avec match .. with ...
En combinant les deux, on obtient une construction très commune :
let rec f (l: 'a list) = match l with
|[] -> (*cas de base, lorsque la liste est vide*)
|x::q -> (*cas récursif : faire quelque chose avec x, et appeler (f q).*)Question 7
Écrivez une fonction récursive nombre_variables_prefixe de type formule_prefixe -> int, qui prend en argument une formule, et renvoie le nombre de variables dans la formule (sans enlever les doublons).
Vous pourrez vous aider d'une fonction auxilliaire est_variable qui renvoie true si le char donné en argument est une variable, et false sinon.
Loading editor...
3. Formules sous forme d'arbres
On va maintenant étudier une autre représentation des formules, plus proche de la forme "habituelle".
Rappels de OCaml : types énumération
En OCaml, il est possible de déclarer des types "énumération", de la manière suivante :
type classe = MP | MPSI | PCSI | PC Un élément de ce type a 4 valeurs possible, une pour chaque variant défini.
On peut aussi ajouter des données à un variant avec le mot-clé of. Par exemple :
type classe = MP | MPSI | PCSI | PC | Autre of string On peut écrire par exemple
let classe_actuelle: classe = MPSI;; Enfin, il est possible stocker une donnée du type lui-même dans un variant ! Par exemple, si on veut redéfinir le type list pour les entiers, on peut écrire
type liste_entier = Vide | Element of (int * liste_entier)Question 8
On a commencé à définir un type formule. Complétez la définition pour ajouter les variants manquants.
Loading editor...
type formule =
Var of char
|Neg of formule
(*à compléter*)
;;Question 9
Définissez une variable formule_test qui contient la version arbre de formule_test_prefixe.
Loading editor...
Rappels de OCaml :
pattern-matching sur des énumérations On peut également faire du pattern-matching sur les énumérations ! Par exemple, avec le type classe de l'exemple précédent, cela donnerait :
let nombre_heure_math (c: classe): int = match c with
|MP -> 10
|MPSI -> 8
|PCSI -> 7
|PC -> 7
|Autre (s) -> (print_string "Classe inconnue : "; print_string s; -1) Je reviendrais sur l'impression et l'utilisation des ; dans la bulle suivante.
Question 10
Écrivez une fonction nombre_variable de type formule -> int qui renvoie le nombre de variables dans un arbre de formule. Vérifiez que votre fonction renvoie bien la même chose sur formule_test que nombre_variable_prefixe sur formule_test_prefixe.
Loading editor...
Rappels de OCaml : instructions et impression
- Pour afficher une valeur en OCaml, il faut utiliser la fonction qui correspond au type de l'objet. Les fonctions disponibles sont :
print_int,print_char,print_string,print_float. Pour revenir à la ligne, on utiliseprint_newline ()(attention à bien ajouter () à la fin !). - En OCaml, certaines fonctions renvoie un type particulier, le type
unit(ce qui signifie en vérité qu'elles ne renvoient rien). En particulier, c'est le cas de toutes les fonctions d'affichage. Il devient alors possible de "chainer" ces fonctions, c'est à dire d'en utiliser plusieurs à la suite. Pour cela, on utilise;pour les séparer. - Lorsqu'on utilise des
;, ou bien plusieursifoumatch, il est bon de mettre des parenthèses pour délimiter les blocs.
Je vous fournis une fonction print_char_list comme exemple. Notez qu'elle renvoie le type unit, c'est à dire qu'elle ne renvoie rien.
Loading editor...
(*affiche une liste de caractères*)
let print_char_list (l: char list): unit =
(*Cette fonction auxiliaire effectue l'affichage en récursif, excépté les crochets*)
let rec aux (l: char list): unit =
match l with
|[] -> ()
|x::q -> (print_char x; print_string ", "; aux q)
in
print_char '['; aux l; print_char ']'; print_newline ()Loading editor...
print_char_list formule_test_prefixe
Question 11
Écrivez une fonction de type formule -> unit qui permet d'afficher un arbre de formule, et testez-là sur formule_test.
Indice
Vous avez plusieurs options :
- afficher l'arbre comme une formule dans la notation usuelle (donc avec les opérateurs entre leurs opérandes)
- (plus compliqué) afficher l'arbre sous forme d'arbre. Je vous conseille alors de calculer au passage la profondeur de chaque noeud, et de l'afficher avec une identation correspondant à sa profondeur.
Loading editor...
Question 12
Écrivez une fonction récursive de type formule -> formule_prefixe qui prend en argument une formule sous forme d'arbre, et renvoie la même formule en notation préfixe.
Donnez sa complexité en temps et en espace.
Loading editor...
4. Evaluation d'une formule
On va procéder à l'évalutation de formules en forme arborescentes. Pour cela, on va utiliser une table de hashage (Hashtbl), qui est une structure associative qui implémente une interface de dictionnaire. L'utilisation d'une Hashtbl n'est pas exigible sans rappel, mais on peut vous la demander en rappelant son interface. C'est donc ce que vous allez faire ici.
Interface de Hashtbl en OCaml
- Création d'une table :
Hashtbl.create ncréé une table avec une capacité initalen, renvoieunit - Ajout d'un élément :
Hashtbl.add table clé valeur, renvoieunit - Récupération d'un élément :
Hashtbl.find table clé, renvoie la valeur associée à la clé (ou une erreur si elle n'existe pas) - Autre option :
Hashtbl.find_opt table clé, renvoieSome (valeur)si la valeur existe, etNonesinon. - Test d'existence :
Hashtbl.mem table clé, renvoietruesi la clé est présente,falsesinon. - Supression :
Hashtbl.remove table clé, renvoieunit. - Parcours :
Hashtbl.iter fonction tableapplique une fonction de typeclé -> valeur -> unità chaque élément de la table.
Question 13
Créer une table de hashage, puis ajoutez-y les correspondances suivantes : | clé | valeur | |---|---------| | 'a' | 3 | | 'b' | 0 | | 'c' | 8 | | 'd' | 1 |
Quel est le type de votre table, après ajout des valeurs ?
Loading editor...
Question 14
Ecrivez une fonction récursive eval de type formule -> (char, int) Hashtbl.t -> int qui renvoie la valeur d'une formule si toutes les variables de la formule sont fixées dans la table, et renvoie une erreur sinon.
Rappels de OCaml : les erreurs
Il y a plusieurs façon de gérer les erreurs en OCaml, mais la manière la plus simple est d'utiliser l'instruction failwith "raison de l'erreur". Elle peut correspondre à tous les types, pour plus de facilité d'utilisation.
Loading editor...
Question 15
Écrivez une fonction eval_prefixe de type formule_prefixe -> (char, int) Hashtbl.t -> int qui renvoie la valeur d'une formule si toutes les variables de la formule sont fixées dans la table, et une erreur sinon.
Indice
Vous pouvez utiliser une fonction auxiliaire récursive qui calcule la valeur de la première formule valable d'une liste, et renvoie cette valeur ainsi que la liste où on a supprimé les caractère correspondants.
Par exemple, pour la liste ["a"; "+"; "b"; "c"], la fonction renverra (3, ["+"; "b"; "c"]). Sur ["+"; "b"; "c"], elle renverra la valeur de +).
Loading editor...
Question 16
Écrivez une fonction de type formule_prefixe -> formule qui convertit une formule en notation préfixe, en une formule sous forme d'arbre.
Indice
C'est presque la même fonction qu'à la question précédente !
Loading editor...
Entrez le code donné en cours pour accéder à la solution :