Skip to main content Informatique en prépa - Lycée Malherbe 

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 l et sa décomposition x::q pour une liste, f pour une formule, i pour 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.

OCaml

Loading editor...

let n: int = 3;;
OCaml

Loading editor...

let k: int = 5;;
OCaml

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".

OCaml

Loading editor...

n + k
OCaml

Loading editor...

pi + pi
OCaml

Loading editor...

pi +. pi
OCaml

Loading editor...

n + pi
OCaml

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 !

OCaml

Loading editor...

let add (x: int) (y: int): int = x + y;;
OCaml

Loading editor...

add n k
OCaml

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 variable add contient une fonction.
OCaml

Loading editor...

let a = (1, 2);;
let b = (4, 5);;
OCaml

Loading editor...

let add2 v1 v2 = 
    let (a1, b1) = v1 in 
    let (a2, b2) = v2 in 
    (a1 + a2, b1 + b2)
;;
OCaml

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).

OCaml

Loading editor...

let f (x: int) (y: int): int = 
    let sum = x + y in 
    let dif = x - y in 
    sum * dif
;;
OCaml

Loading editor...

let abs x = 
    if x < 0 then -x else x
;;
OCaml

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
;;
Python

Loading editor...

# vos équivalents en Python
Question 3

Traduisez les fonctions python suivantes en OCaml.

Python

Loading editor...

def est_pair(x): 
    return x % 2 == 0

est_pair(6)

x % 2 en OCaml s'écrit x mod 2.

OCaml

Loading editor...

Python

Loading editor...

def produit_scalaire(v1, v2): 
    (a, b) = v1
    (c, d) = v2
    return a*c + b*d

produit_scalaire((1, 2), (3, 4))
OCaml

Loading editor...

II. Formules et notation préfixe

1. Définitions

Une formule est définie inductivement pour un ensemble de variables comme

Par exemple, est une formule.

On appelle valuation des variables d'une formule une fonction qui attribue une valeur entière à chaque variable.

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 au lieu de (et de même pour ).

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::l1 est la liste l1 à laquelle on a ajouté x au début
  • l1 @ l2 est la concaténation de l1 et l2

À noter qu'on n'a pas d'opérateur pour accéder au ième élément d'une liste !

OCaml

Loading editor...

type formule_prefixe = char list;;

Dans une formule, le caractère * représente le , + représente le , et - représente le . Tous les autres symboles représentent des variables.

Par exemple, la formule en notation préfixe, sera représentée par la liste ['+'; '-'; '*'; 'a'; '-'; 'b'; '*'; '-'; 'a'; 'b']

OCaml

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.

OCaml

Loading editor...

(*Renvoie la négation d'une formule*)
let neg (f: formule_prefixe): formule_prefixe = '-'::f
OCaml

Loading editor...

(*Renvoie une formule consituée d'une seule variable*)
let var (v: char): formule_prefixe = (*à compléter*)
OCaml

Loading editor...

(*Renvoie la multiplication de deux formules*)
let mul (f1: formule_prefixe) (f2: formule_prefixe): formule_prefixe = (*à compléter*)
OCaml

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.

OCaml

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.

OCaml

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.

OCaml

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.

OCaml

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.

OCaml

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 utilise print_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 plusieurs if ou match, 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.

OCaml

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 ()
OCaml

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.
OCaml

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.

OCaml

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 n créé une table avec une capacité initale n, renvoie unit
  • Ajout d'un élément : Hashtbl.add table clé valeur, renvoie unit
  • 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é, renvoie Some (valeur) si la valeur existe, et None sinon.
  • Test d'existence : Hashtbl.mem table clé, renvoie true si la clé est présente, false sinon.
  • Supression : Hashtbl.remove table clé, renvoie unit.
  • Parcours : Hashtbl.iter fonction table applique une fonction de type clé -> 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 ?

OCaml

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.

OCaml

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 (après avoir faire deux appels récursifs pour les arguments du +).

OCaml

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 !

OCaml

Loading editor...


Entrez le code donné en cours pour accéder à la solution :