Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • programmation-fonctionnelle/lifpf
  • p2208151/lifpf
  • p2402635/lifpf
  • p2103541/lifpf
  • p2105194/lifpf
  • p2210488/lifpf
  • p2212652/lifpf
  • p2019985/lifpf
  • p2208660/lifpf
  • p2102825/lifpf
  • p2002599/lifpf
  • p2106670/lifpf
  • p2103270/lifpf
  • p2210897/lifpf-demo
  • p2110686/lifpf-aysoy
  • p2110638/lifpf
  • p2202556/lifpf
17 results
Show changes
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
File added
File added
......@@ -6,19 +6,23 @@ Les TPs s'effectueront sous Linux.
Vérifier que `ocaml` est disponible.
TODO: ajouter les instructions pour le opam partagé
Configurer votre environnement selon le fichier [CONFIGURATION.md](../CONFIGURATION.md).
Vérifier que l'extension "OCaml Platform" est installée dans VSCode, l'installer si besoin.
Créer un fichier `tp1.ml`, ouvrir ce fichier avec Visual Studio Code.
Le plugin OCaml permet d'exécuter un code sélectionné avec les touches Shift Enter.
Le plugin OCaml permet d'exécuter un code sélectionné avec les touches `[Shift] + [Enter]`.
## 1. Premiers pas avec l'interpréteur
## Partie 1
Lancer l'interpréteur ocaml avec la commande
Quand vous aurez fini cette partie, prévenez votre chargé de TP.
### 1.1. Premiers pas avec l'interpréteur
Lancer l'interpréteur `ocaml` avec la commande
```bash
rlwrap ocaml
utop
```
ou simplement
......@@ -27,12 +31,12 @@ ou simplement
ocaml
```
si `rlwrap` n'est pas installé.
si `utop` n'est pas installé (sur la machine de l'université, cela veut dire que vous n'avez probablement pas correctement suivi les instructions de [CONFIGURATION.md](../CONFIGURATION.md)).
### 1.1. Types de base
#### 1.1.1. Types de base
On va commencer par utiliser l'interpréteur comme une calculatrice avancée.
Dans l'interpréteur, les expressions à calculer doivent se terminer par `;;`.
Dans l'interpréteur, les expressions à calculer **doivent** se terminer par `;;`.
> Saisir quelques opérations sur les entiers, par exemple:
>
......@@ -56,7 +60,7 @@ Il est évidement possible d'utiliser les booléens en OCaml. Les constantes son
> Écrire une ou deux expressions booléennes dans l'interpréteur. Indiquer qui de `||` ou `&&` est prioritaire (c.-à-d. où sont les parenthèses dans une expression comme `a || b && c`)
Les opérateurs de comparaison s'écrivent `=` (attention il y a bien un seul `=`), `!=`, `>`, `<`, `>=`, `<=`. Ils permettent de comparer des valeurs de même type.
Les opérateurs de comparaison s'écrivent `=` (attention il y a bien un seul signe `=`), `!=`, `>`, `<`, `>=`, `<=`. Ils permettent de comparer des valeurs de même type.
> Expliquer la différence de comportement entre ces deux expressions:
>
......@@ -70,7 +74,7 @@ Les opérateurs de comparaison s'écrivent `=` (attention il y a bien un seul `=
> 3 < 2.0;;
> ```
### 1.2. Appels de fonction
#### 1.1.2. Appels de fonction
En OCaml, les appels de fonctions sont sous la forme `f a1 a2 a3``f` est la
fonction et `a1`, `a2` et `a3` sont les arguments (les paramètres effectifs)
......@@ -85,7 +89,7 @@ La fonction `float_of_int` permet de transformer un `int` en `float`.
> Dans l'interpréteur, appeler la fonction `float_of_int` en lui passant un `int` en argument.
> Essayer ensuite avec un `float` et expliquer ce qui c'est passé.
>
> Entrer l'expression `int_of_float ;;` dans l'interpréteur.
> Celui-ci affiche alors le type de la fonction.
......@@ -98,7 +102,7 @@ La négation dans les booléens n'est pas un opérateur, mais simplement la fonc
> Expliquer le résultat de l'évaluation de `not false && false` en ajoutant les parenthèses implicites au bon endroit.
Remarque: les opérateurs en OCaml sont aussi des fonctions. En ajoutant des parenthèses directement autour d'un opérateur, il sera vu comme une fonction, par exemple on peut écrire `(&&) true false` au lieu de `true && false`.
Remarque : les opérateurs en OCaml sont **aussi** des fonctions. En ajoutant des parenthèses directement autour d'un opérateur, il sera vu comme une fonction, par exemple on peut écrire `(&&) true false` au lieu de `true && false`.
> Récrire l'expression suivante en utilisant les opérateurs comme des fonctions:
>
......@@ -106,7 +110,7 @@ Remarque: les opérateurs en OCaml sont aussi des fonctions. En ajoutant des par
> 2 < 3 && "b" < "ab"
> ```
### 1.3. Variables
#### 1.1.3. Variables
Pour rappel, la syntaxe des définitions de variables est :
......@@ -121,9 +125,8 @@ let x1 = 3.5 +. float_of_int 2
in x1 +. 3.0;;
```
La construction `let in` est elle-même une expression et peut donc être utilisée
dans d'autres expressions, y compris d'autres `let ... in ...`.
Cela permet en particulier de définir plusieurs variables à la suite:
La construction `let ... in ...` est elle-même une expression et peut donc être utilisée dans d'autres expressions, y compris d'autres `let ... in ...`.
Cela permet en particulier de définir plusieurs variables à la suite :
```ocaml
let x1 = 3.5 +. float_of_int 2 in
......@@ -131,9 +134,9 @@ let x2 = x1 *. x1 in
x2 *. 2.0 ;;
```
### 1.4 Conditionnelle
#### 1.1.4 Conditionnelle
La conditionnelle `if .. then ... else ...` est une **expression** et pas une _instruction_ en OCaml, c'est à dire qu'elle a un résultat.
La conditionnelle `if ... then ... else ...` est une **expression** et pas une _instruction_ en OCaml, c'est-à-dire qu'elle a un résultat.
> **Prédire** puis vérifier le résultat de l'expression suivante:
>
......@@ -141,9 +144,9 @@ La conditionnelle `if .. then ... else ...` est une **expression** et pas une _i
> (if true then 3 else 5) > 4
> ```
## 2. Définition de fonctions
### 1.2. Définition de fonctions
La construction `let` sans le `in` permet de définir des variables globales.
La construction `let` _sans_ le `in` permet de définir des variables _globales_.
On peut l'utiliser pour définir des fonctions comme suit:
```ocaml
......@@ -162,7 +165,7 @@ let f x = x + 1;;
>
> Quel est le type affiché par OCaml à la définition de cette fonction ?
Il est possible d'imposer les types des arguments avec la syntaxe suivante:
Il est possible d'imposer les types des arguments avec la syntaxe suivante :
```ocaml
let f (a1:t1) (a2:t2): type_retour = valeur_de_retour;;
......@@ -175,7 +178,7 @@ Récrire une deuxième fois cette fonction en changeant le type de `a` en `int`
> Dans la suite de ce TP et dans les TPs suivants, on prendra soin de **toujours indiquer le type des fonctions** définies globalement.
OCaml est muni d'un outil, ocamldoc, qui permet de générer de la documentation à partir de commentaires spéciaux.
OCaml est muni d'un outil, `ocamldoc`, qui permet de générer de la documentation à partir de commentaires spéciaux.
Voici une exemple de documentation à placer juste avant la définition de discriminant:
......@@ -190,13 +193,14 @@ Cette fonction est utile pour trouver les racines d'un trinôme.
*)
```
> Dans la suite de ce TP et pour les autres TP, on écrira toujours un commentaire spécial avant la définition d'une fonction définie globalement. Et on écrira toujours ce **commentaire avant de commencer à coder** la fonction.
**Dans la suite de ce TP et pour les autres TP**, on écrira _toujours_ un commentaire spécial avant la définition d'une fonction définie globalement. Et on écrira toujours ce **commentaire avant de commencer à coder** la fonction.
## 3. Types somme
### 1.3. Types somme
### 3.1 Définitions de types somme
#### 1.3.1 Définitions de types somme
Les types somme en OCaml sont des types dont les valeurs peuvent être construites de différentes manières. L'exemple suivant illustre la syntaxe de définition de ces types:
Les types somme en OCaml sont des types dont les valeurs peuvent être construites de différentes manières.
L'exemple suivant illustre la syntaxe de définition de ces types :
```ocaml
type couleur = Rouge | Jaune | Bleu;;
......@@ -239,9 +243,9 @@ DeuxInt (3,4);;
Remarque: quand on passe plusieurs arguments à un constructeur, ils sont passés sous forme de n-uplet (une paire pour `DeuxInt` et un triplet pour `RJB`). Les n-uplets sont prédéfinis en OCaml et possèdent leur propre type dont la syntaxe est `*`
> En utilisant l'interpréteur, indiquer quel est le type de `(3, 5.6)`. Quelle est la différence avec `(3,5,6)` ?
> En utilisant l'interpréteur, indiquer quel est le type de `(3, 5.6)`. Quelle est la différence avec `(3, 5, 6)` ?
### 3.2 Pattern matching
#### 1.3.2 Pattern matching
OCaml possède la construction `match ... with ...` qui permet de décider de l'expression à évaluer en fonction de la valeur de l'expression indiquée dans le `match`. Par exemple:
......@@ -258,7 +262,7 @@ s'évalue en `"rouge"` si `c` contient `Rouge` ou encore sur `"mélange"` si `c`
> Définir une fonction `nom_couleur` qui donne le nom d'une couleur sous forme de `string` si c'est une des 6 couleurs nommées ou la chaîne "mélange" par défaut, c'est-à-dire si la couleur est définie par le constructeur `RJB`.
>
> Réécrire la fonction `nom_couleur` en supprimant le dernier cas associé à la valeur "mélange", quelle erreur survient ?
>
> Faire une nouvelle version en plaçant le cas `_` en premier, quel avertissement l'interpréteur affiche-t-il ? Que peut-on en déduire sur l'ordre des cas dans un `match` ?
Dans le cas d'un constructeur avec des arguments, il est souvent utile de pouvoir récupérer la valeur de ceux-ci dans le pattern matching.
......@@ -288,7 +292,12 @@ On veut implémenter un générateur pour les paroles de la chanson [99 bottles
> utilisera `match` pour distinguer les cas 0, 1 ou 2 bouteilles du cas général
> à _n_ bouteilles.
## 4. Récursivité
Fin de la partie 1.
Si vous avez fini en séance, prévenez votre chargé de TP qui va vérifier que vous avez compris.
## Partie 2
### 2.1. Récursivité
Dans le cas de fonctions récursives, il faut ajouter le mot clé `rec` après le `let`:
......@@ -306,7 +315,7 @@ let rec sum_n (n:int): int =
> Écrire une fonction `factorielle` qui calcule le produit des entiers entre 1 et _n_.
## 5. Listes
### 2.2. Listes
OCaml possède une structure de liste prédéfinie. La liste vide d'écrit `[]` et
l'ajout en tête de liste est réalisé par l'opérateur `::`. Cet opérateur est
......@@ -343,14 +352,14 @@ let rec longueur (l: string list): int =
;;
```
> Expliquer pourquoi on a indiqué \_ au lieu d'une variable dans le deuxième cas du `match`.
> Écrire une fonction sum_f qui fait la somme des éléments d'une `float list`.
> Expliquer pourquoi on a indiqué `_` au lieu d'une variable dans le deuxième cas du `match`.
>
> Écrire une fonction `sum_f` qui fait la somme des éléments d'une `float list`.
Il peut parfois être utile dans un `match` sur une liste pour faire un cas
particulier avec des liste de taille 1, 2, etc.
> Utiliser la possibilité de faire un `match` avec une cas pour les listes de
> Utiliser la possibilité de faire un `match` avec un cas pour les listes de
> taille 1 (en plus du cas liste vide et du cas liste de taille >= 2) pour
> écrire une fonction `join_s` qui prend en argument un séparateur (de type
> `string`) et une liste de `string` et renvoie les éléments de la liste
......@@ -370,8 +379,10 @@ valeur de liste par cas avec un `match` et fabriquer une liste comme résultat.
> la liste composée des paragraphes de la chanson _99 bottles of beer_
> correspondant à chacun des éléments de la liste passée en paramètre. On pourra
> bien entedu réutiliser la fonction `paragraphe_bottles`.
>
> Créer une variable globale `chanson_99_bottles` dont la valeur est les paroles
> complètes de la chanson _99 bottles of beer_. On séparera chaque paragraphe du
> suivant avec "\n\n". Vérifier que la variable a la bonne valeur en l'affichant
> avec `print_endline chanson_99_bottles;;` .
Fin de la 2eme partie et du TP. Prévenez votre chargé de TP si vous avez fini cette partie en séance.
(**********************************************************************)
(* 1.1 *)
(* 1.1.1 *)
(3 * 2) + (5 / 2);;
3.2 +. (7.4 /. 2.0);;
"AC" ^ "/" ^ "DC";;
......@@ -11,7 +11,7 @@ true || (false && false);;
(* 3 < 2.0 provoque une erreur car 3 et 2.0 n'ont pas le même type *)
(**********************************************************************)
(* 1.2 *)
(* 1.1.2 *)
(* float_of_int 3.0 provoque une erreur car 3.0 n'est pas un int *)
float_of_int 3;;
......@@ -22,7 +22,7 @@ float_of_int (int_of_float 3.6 + 2) +. 1.5;;
2 < 3 && "b" < "ab";;
(**********************************************************************)
(* 1.3 *)
(* 1.1.3 *)
let x1 = 3.5 +. float_of_int 2 in
x1 +. 3.0
;;
......@@ -33,12 +33,12 @@ x2 *. 2.0
;;
(**********************************************************************)
(* 1.4 *)
(* 1.1.4 *)
(if true then 3 else 5) > 4
(**********************************************************************)
(* 2 *)
(* 1.2 *)
let f x = x + 1;;
3 + f 3
......@@ -47,14 +47,12 @@ let discriminant a b c = (b *. b) -. (4.0 *. a *. c)
(* Le type affiche par l'interpréteur est float -> float -> float -> float *)
(**
[discriminant a b c] calcule le discriminant d'un trinôme.
Cette fonction est utile pour trouver les racines d'un trinôme.
@param a le coefficient d'ordre 2
@param b le coefficient d'ordre 1
@param c le coefficient d'ordre 0
@return le discriminant
*)
(** [discriminant a b c] calcule le discriminant d'un trinôme. Cette fonction
est utile pour trouver les racines d'un trinôme.
@param a le coefficient d'ordre 2
@param b le coefficient d'ordre 1
@param c le coefficient d'ordre 0
@return le discriminant *)
let discriminant (a : float) (b : float) (c : float) : float =
(b *. b) -. (4.0 *. a *. c)
;;
......@@ -70,7 +68,7 @@ discriminant 2.0 8.0 8.0
(**********************************************************************)
(* 3.1 *)
(* 1.3.1 *)
type couleur = Rouge | Jaune | Bleu;;
(* La valeur Rouge est bien définie et reconnue comme étant de type couleur *)
......@@ -102,7 +100,7 @@ type couleur =
3, 5, 6
(**********************************************************************)
(* 3.2 *)
(* 1.3.2 *)
let nom_couleur c =
match c with
......@@ -141,11 +139,10 @@ let nom_couleur c =
Elle indique que le match ne couvre pas tous les cas possibles.
*)
(**
Génère le paragraphe pour [n] bouteilles de bières.
@param n le nombre de bouteilles de bièere sur le mur au début du paragraphe.
@return le paragraphe
*)
(** Génère le paragraphe pour [n] bouteilles de bières.
@param n
le nombre de bouteilles de bièere sur le mur au début du paragraphe.
@return le paragraphe *)
let paragraphe_bottles (n : int) : string =
match n with
| 0 ->
......@@ -172,53 +169,45 @@ paragraphe_bottles 2;;
paragraphe_bottles 7
(**********************************************************************)
(* 4 *)
(* 2.1 *)
(**
Renvoie la somme des n premiers entiers
@param n le nombre d'entiers à sommer
@return la somme
*)
(** Renvoie la somme des n premiers entiers
@param n le nombre d'entiers à sommer
@return la somme *)
let rec sum_n (n : int) : int = if n <= 0 then 0 else n + sum_n (n - 1)
(**
[factorielle n] calcule la factorielle des [n] premiers entiers
@param n doit être supérieur ou égal à 1
@return !n
*)
(** [factorielle n] calcule la factorielle des [n] premiers entiers
@param n doit être supérieur ou égal à 1
@return !n *)
let rec factorielle n = if n <= 1 then 1 else n * factorielle (n - 1)
(**********************************************************************)
(* 5 *)
(* 2.2 *)
(* Erreur car on essaie d'ajouter une string dans une int list
"2" :: 3 :: [];;
*)
(**
Cette fonction calcule la longueur d'une liste de `string`.
@param l la liste dont on veut la longueur
@return la longueur de l
*)
(** Cette fonction calcule la longueur d'une liste de `string`.
@param l la liste dont on veut la longueur
@return la longueur de l *)
let rec longueur (l : string list) : int =
match l with [] -> 0 | _ :: l2 -> 1 + longueur l2
(* La valeur de l'élément de list n'est pas utilisée dans le deuxième cas du match.
On a donc pas besoin de la recupérer dans une variable et on met _ pour plus de clarté. *)
(**
Cette fonction calcule la somme des éléments de la liste
(** Cette fonction calcule la somme des éléments de la liste
@param l la liste dont on veut additionner les éléments
@return la somme des éléments de la liste
*)
@return la somme des éléments de la liste *)
let rec sum_f (l : float list) : float =
match l with [] -> 0.0 | x :: l2 -> x +. sum_f l2
(**
Cette fonction concatène les éléments de la liste en les séparant par [sep].
@param l la liste dont on veut concaténer les éléments
@param sep le séparateur
@return les éléments de la liste [l] concaténé et séparés les uns des autres par [sep]
*)
(** Cette fonction concatène les éléments de la liste en les séparant par [sep].
@param l la liste dont on veut concaténer les éléments
@param sep le séparateur
@return
les éléments de la liste [l] concaténé et séparés les uns des autres par
[sep] *)
let rec join_s (l : string list) (sep : string) : string =
match l with
| [] -> ""
......@@ -233,11 +222,9 @@ let rec join_s (l : string list) (sep : string) : string =
(* test *)
join_s [ "a"; "b"; "c" ] "|"
(**
Cette fonction créée une liste des int de n à 0 inclus.
@param n l'int de début de liste
@return la liste des entiers décroissants
*)
(** Cette fonction créée une liste des int de n à 0 inclus.
@param n l'int de début de liste
@return la liste des entiers décroissants *)
let rec liste_n_0 (n : int) : int list =
if n <= 0 then [ 0 ] else n :: liste_n_0 (n - 1)
;;
......@@ -245,12 +232,10 @@ let rec liste_n_0 (n : int) : int list =
(* test *)
liste_n_0 12
(**
[bottles_of_list [n1; n2; ...]] crée la liste des paragraphes de la chanson 99
bottles of beer pour n1 puis n2 ... bouteilles.
@param l la liste des nombres de bouteilles à transformer
@return la liste des paragraphes correspondant aux int de l
*)
(** [bottles_of_list [n1; n2; ...]] crée la liste des paragraphes de la chanson
99 bottles of beer pour n1 puis n2 ... bouteilles.
@param l la liste des nombres de bouteilles à transformer
@return la liste des paragraphes correspondant aux int de l *)
let rec bottles_of_list (l : int list) : string list =
match l with
| [] -> []
......
# LIFPF: TP2 récursion sur les listes
<<<<<<< Updated upstream
Finir le [TP1](tp1.md) avant de commencer ce TP.
=======
Finir le [TP1](../TP1/tp1.md) avant de commencer ce TP.
>>>>>>> Stashed changes
Le reste du TP consiste à travailler la récursion sur les listes.
Ce TP consiste à travailler la récursion sur les listes.
Comme dans le TP précédent, on prendra soin:
- de **donner le type** des arguments et le type de retour de chaque fonction;
......@@ -43,18 +37,20 @@ applatit [ [ 1; 2 ]; [ 3; 4; 5 ]; []; [ 6 ] ] = [ 1; 2; 3; 4; 5; 6 ];;
applatit [ [ 1 ] ] = [ 1 ];;
```
Faire une deuxième version `applatit2` ayant la même spécification que `applatit` mais qui n'utilise **pas** `concatene`.
Faire une deuxième version `applatit2` ayant la même spécification que `applatit` mais qui n'utilise **pas** `concatene` (et qui n'utilise aucune autre fonction, en particulier pas l'opérateur de concaténation prédéfini en OCaml).
Tester cette fonction avec les mêmes tests que `applatit`.
> Fin de la partie 1, prévenez votre chargé de TP.
## 2. Retournement de liste
On souhaite implémenter une fonction `renverse` qui renverse une liste.
L'algorithme naïf pour faire une inversion de liste consiste à inverser la queue de liste et à concaténer la tête de liste à la fin de la queue renversée.
Cet algorithme est cependant inefficace car il conduit à effectuer un nombre _quadratique_ (en fonction de la taille de la liste initiale) d'ajouts en tête de liste à cause de l'opération de concaténation.
Cet algorithme est cependant inefficace, car il conduit à effectuer un nombre _quadratique_ (en fonction de la taille de la liste initiale) d'ajouts en tête de liste à cause de l'opération de concaténation.
On veut donc implémenter un algorithme plus efficace qui ne fait qu'un nombre _linéaire_ d'ajouts en tête de liste.
Pour cela on code une version modifiée `renverse_ajoute` qui prend **deux** listes en argument et renvoie la première renversée concaténée à la seconde, par exemple
Pour cela, on code une version modifiée `renverse_ajoute` qui prend **deux** listes en argument et renvoie la première renversée concaténée à la seconde, par exemple
```ocaml
renverse_ajoute [ 1; 2; 3 ] [ 4; 5; 6 ] = [ 3; 2; 1; 4; 5; 6]
......@@ -74,7 +70,9 @@ On peut aussi remarquer que `renverse` peut se coder très facilement à partir
Coder `renverse` en définissant `renverse_ajoute`.
Définir tout d'abord `renverse_ajoute` avant `renverse` et tester. Essayer ensuite d'intégrer la définition de `renverse_ajoute` à l'intérieur de celle de renverse via un `let rec renverse_ajoute ... in ...`.
## 3. Tri par insersion
> Fin de la partie 2, prévenez votre chargé de TP
## 3. Tri par insertion
On code [l'algorithme de tri par insertion](https://fr.wikipedia.org/wiki/Tri_par_insertion) qui consiste, pour chaque élément, à l'insérer à la bonne place dans le reste de la liste préalablement triée.
......@@ -100,16 +98,19 @@ tri_insertion [ 1; 4; 2; 3 ] = [ 1; 2; 3; 4 ];;
tri_insertion [ 1; 2; 3; 4 ] = [ 1; 2; 3; 4 ];;
```
> Fin de la 3eme partie, prévenez votre chargé de TP
## 4. Recherche dans une liste d'association
On rappelle qu'une liste d'association est _une liste de paires `(clé, valeur)`_. L'opération principale sur les listes d'association est de chercher la valeur associée à une clé, si elle existe.
On rappelle qu'une liste d'association est _une liste de paires `(clé, valeur)`_.
L'opération principale sur les listes d'association est de chercher la valeur associée à une clé, si elle existe.
Dans cette question on considèrera que les clés sont de type `int` et les valeurs de type `string`.
Dans cette question, on considèrera que les clés sont de type `int` et les valeurs de type `string`.
### 4.1 Type résultat
Comme la clé n'est pas forcément présente dans la liste, on commence par créer un type somme `resultat` avec un constructeur représentant l'absence de valeur et un constructeur représentant le cas où une valeur a été trouvée.
Pour cela il faut essentiellement se poser la question des données éventuellement associées à chaque constructeur, ainsi que de leur type.
Pour cela, il faut essentiellement se poser la question des données éventuellement associées à chaque constructeur, ainsi que de leur type.
### 4.2 Fonction de recherche
......@@ -117,9 +118,11 @@ Coder la fonction `cherche` qui va chercher une clé dans une liste d'associatio
Bien penser à écrire des tests pour cette fonction.
> Fin de la 4eme partie, prévenez votre chargé de TP.
## 5. Calculatrice en notation polonaise
On souhaite implémenter une calculatrice en notation polonaise, c'est-à-dire calculant le résultat d'expressions dans lesquelles les **opérateurs sont placés avant leur arguments**.
On souhaite implémenter une calculatrice en notation polonaise, c'est-à-dire calculant le résultat d'expressions dans lesquelles les **opérateurs sont placés avant leurs arguments**.
On donne quelques exemples dans le tableau suivant:
| Notation habituelle | Notation polonaise |
......@@ -150,7 +153,9 @@ type resultat = Ok of int | ErrDivZero | ErrExpr
### 5.1 Évaluation des opérations
Afin de simplifier le code de l'évaluateur, on commence par créer une fonction `eval_op` qui va évaluer le résultat d'un opérateur appliqué à des valeurs.
Comme il faut prendre en compte les erreurs de division par zéro, on renverra un `resultat` et pas un `int`. De plus comme cette fonction sera utilisée avec des arguments qui peuvent eux-même être obtenu via l'évaluation de d'autres expressions, on va prendre en argument des `resultat` et pas simplement des `int`. Si un résultat est une erreur, on renverra cette erreur au lieu d'effectuer le calcul. Ainsi le type de `eval_op` devra être `binop -> resultat -> resultat -> resultat`.
Comme il faut prendre en compte les erreurs de division par zéro, on renverra un `resultat` et pas un `int`.
De plus comme cette fonction sera utilisée avec des arguments qui peuvent eux-mêmes être obtenus via l'évaluation d'autres expressions, on va prendre en argument des `resultat` et pas simplement des `int`.
Si un résultat est une erreur, on renverra cette erreur au lieu d'effectuer le calcul. Ainsi le type de `eval_op` devra être `binop -> resultat -> resultat -> resultat`.
Quelques tests à compléter:
......@@ -163,12 +168,13 @@ eval_op Div (Ok 5) ErrExpr = ErrExpr;;
### 5.2 Évaluation d'une suite d'expression
Une suite d'expressions est représentée par une liste d'éléments d'expression. On peut remarquer qu'une telle liste peut contenir plusieurs expressions, par exemple `+ 3 5 - 2 7`, est représentée par `[ Op Plus; Cst 3; Cst 5; Op Moins; Cst 2; Cst 7 ]` et correspond aux deux expressions écrites habituellement `3+5` et `2-7`.
Une suite d'expressions est représentée par une liste d'éléments d'expression. On peut remarquer qu'une telle liste peut contenir plusieurs expressions, par exemple `+ 3 5 - 2 7`, est représentée par `[ Op Plus; Cst 3; Cst 5; Op Moins; Cst 2; Cst 7 ]` et correspond aux deux expressions écrites habituellement `3 + 5` et `2 - 7`.
On va coder une fonction `eval_expr` qui va évaluer une suite d'expressions et renvoyer une liste de résultats. Pour y arriver on peut faire les remarques suivantes:
On va coder une fonction `eval_expr` qui va évaluer une suite d'expressions et renvoyer une liste de résultats.
Pour y arriver on peut faire les remarques suivantes:
- Il faut toujours évaluer le reste d'une suite d'expressions non vide.
- L'évaluation d'une constante est cette constante mais sous forme de résultat
- L'évaluation d'une constante est cette constante, mais sous forme de résultat.
- L'évaluation d'un opérateur binaire nécessite de récupérer le résultat des deux expressions suivantes, donc d'avoir déjà fait l'appel récursif pour évaluer le reste des expressions.
- Si l'appel récursif produit une liste de résultats contenant zéro ou un élément, il n'est pas possible d'évaluer un opérateur binaire. C'est le cas où l'expression est mal construite.
......@@ -180,3 +186,5 @@ eval_expr [ Op Mult; Cst 3; Cst 2 ] = [ Ok 6 ];;
eval_expr [ Op Plus; Cst 3; Cst 5; Op Moins; Cst 2; Cst 7 ] = [ Ok 8; Ok (-5) ];;
eval_expr [ Op Plus; Op Div; Cst 7; Cst 3 ] = [ ErrExpr ];;
```
> Fin de la 5eme partie, prévenez votre chargé de TP.
......@@ -2,15 +2,11 @@
**Lire complètement** chaque partie avant de la coder.
## 1. Retour sur le TP2
Finir les exercices du [TP2](tp2.md) jusqu'à 4.2 inclus.
## 2. Arbres binaires
## 1. Arbres binaires
Dans cette partie on va travailler sur les arbres binaires.
### 2.1 Quelques définitions simples
### 1.1 Quelques définitions simples
> Définir un type `arbre_bin` pour représenter les arbres binaires d'entiers (type `int`) avec un constructeur `ABVide` pour l'arbre vide et un constructeur `ABNoeud` pour les noeuds interne (qui contiendra un `int` et les deux arbres fils).
> Définir également quelques variables globales d'arbres binaires pour faciliter le test des fonctions à venir.
......@@ -25,7 +21,9 @@ assert (taille_ab ab2 = 2);;
> Définir la fonction `produit_ab` qui effectue le produit des éléments d'un arbre binaire d'`int`. On prendra la convention que le produit d'un arbre vide est 1 (expliquer pourquoi on a choisi cette valeur). Tester la fonction avec des `assert`.
### 2.2 Arbres binaires de recherche
Prévenez votre chargé de TP pour qu'il valide la partie 1.1.
### 1.2 Arbres binaires de recherche
Quelques rappels sur les arbres binaires de recherche:
......@@ -33,7 +31,7 @@ Quelques rappels sur les arbres binaires de recherche:
- Pour insérer un élément dans un arbre binaire de recherche on peut l'insérer récursivement à droite ou à gauche selon qu'il est plus petit ou plus grand que l'élément du noeud dans lequel on veut faire l'insersion.
- Dans un parcours _infixe_ d'un arbre binaire, on regarde d'abord les éléments du fils gauche, puis l'élément du noeud, puis les éléments du fils droit. Si on effectue un parcours infixe d'un arbre binaire de recherche, alors on visite les éléments de l'arbre **dans l'ordre croissant**.
#### 2.2.1 Insertion
#### Insertion
> Écrire une fonction `insere_arbre_bin_recherche` qui insère une élément dans un arbre binaire de recherche. Précisez en commentaire ce qui est fait lorsque l'élément à ajouter est déjà présent dans l'arbre.
......@@ -41,7 +39,11 @@ On rappelle que dans le cadre de l'UE, on ne fait **pas** de modification en pla
> Tester `insere_arbre_bin_recherche` en utilisant `assert`.
#### 2.2.2 Transformation d'arbre
Prévenez votre chargé de TP pour qu'il valide la partie 1.2.
### 1.3 Tri
#### 1.3.1 Transformation d'arbre en liste
> Écrire une fonction `list_of_arbre_bin` qui calcule la liste contenant les éléments d'un arbre binaire. Cette liste devra correspondre à un parcours infixe de l'arbre. Ainsi, si l'arbre est _de recherche_ les entiers de la liste obtenue doivent être croissants.
......@@ -50,7 +52,7 @@ Il faudra bien réfléchir à la position de l'élément du noeud dans le résul
> Tester la fonction `list_of_arbre_bin` avec `assert`.
#### 2.2.3 Transformation d'arbre
#### 1.3.2 Transformation inverse et tri
> Écrire la fonction `arbre_bin_rech_of_int_list` qui transforme une liste en arbre binaire **de recherche**.
......@@ -62,7 +64,9 @@ Quelle propriété des arbres est-elle utile pour prédire le résultat de l'app
> Coder la fonction `tri_abr` qui utilise des fonctions codées dans ce TP pour trier une liste d'int. Tester cette fonction avec `assert`.
## 3. Évaluation d'expressions arithmétiques
Prévenez votre chargé de TP pour qu'il valide la partie 1.3.
## 2. Évaluation d'expressions arithmétiques
Dans cette partie, on va implémenter un évaluateur d'expressions arithmétiques.
......@@ -82,27 +86,31 @@ type expr =
> Créer quelques variables globales contenant des expressions pour les tests à venir.
### 3.O Affichage d'expressions simples
### 2.1 Affichage et évaluation
> Coder la fonction `string_of_expr` qui prend en argument une expression et la transforme en une chaîne de caractères, les expressions étant notées en notation infixe et complètement parenthésées, comme par exemple `"(3 + (2 * 4))"`.
#### 2.1.1 Affichage d'expressions simples
> Coder la fonction `string_of_expr` qui prend en argument une expression et la transforme en une chaîne de caractères, les expressions étant notées en notation infixe et complètement parenthésées, par exemple `"(3 + (2 * 4))"`.
>
> Tester cette fonction en utilisant `assert` et les variables globales précédement créées.
> Tester cette fonction en utilisant `assert` et les variables globales précédemment créées.
### 3.1 Évaluation d'expressions simples
#### 2.1.2 Évaluation d'expressions simples
> Coder la fonction `eval_expr` qui prend en argument une expression et renvoie le résultat de son évaluation (un `int`).
>
> Tester cette fonction en utilisant `assert` et les variables globales précédement créées.
> Tester cette fonction en utilisant `assert` et les variables globales précédemment créées.
Prévenez votre chargé de TP afin de valider la partie 2.1.
### 3.2 Division et erreurs
### 2.2 Division et erreurs
#### 3.2.1 Ajout de l'opérateur Div
#### 2.2.1 Ajout de l'opérateur Div
On souhaite ajouter la division aux opérateurs possibles pour une expression. Pour cela il faut bien sûr _ajouter un constructeur_ au type `binop`.
> Modifier le type `binop` pour ajouter la division. Modifier la fonction `eval_expr` pour intégrer le cas de la division sans tenir compte des problèmes de division par zéro (pour le moment).
#### 3.2.2 Gestion de la division par zéro
#### 2.2.2 Gestion de la division par zéro
La division par zéro produit une erreur.
Plutôt que de laisser l'erreur survenir lors de la division entre `int`, on va l'anticiper en gérant la possibilité qu'une expression s'évalue en une erreur et pas en un `int`.
......@@ -121,15 +129,19 @@ type resultat = Ok of int | Err of eval_err
> Modifier la fonction `eval_expr` pour qu'elle renvoie un `resultat` et pas un `int`.
Pour y arriver, on commencera par modifier la signature et renvoyer des valeurs contruites avec `Ok` pour les cas où il n'y a **pas** d'erreur. On réfléchira ensuite aux modfications nécessaires pour prendre en compte le fait que les appels récursifs produisent maintenant des `resultat` et pas des `int`. On utilisera le pattern matching pour distinguer les cas où l'évaluation d'une sous-expression produit une erreur des cas où elle se passe bien.
Pour y arriver, on commencera par modifier la signature et renvoyer des valeurs construites avec `Ok` pour les cas où il n'y a **pas** d'erreur. On réfléchira ensuite aux modifications nécessaires pour prendre en compte le fait que les appels récursifs produisent maintenant des `resultat` et pas des `int`. On utilisera le pattern matching pour distinguer les cas où l'évaluation d'une sous-expression produit une erreur des cas où elle se passe bien.
Enfin on ajoutera le test de la valeur du diviseur pour produire une erreur s'il vaut zéro.
> Modifier vos tests de la fonction `eval_expr` pour les adapter aux changements précédents. Ne pas oublier de tester la division et la division par zéro.
### 3.3 Variables
Prévenez votre chargé de TP pour valider la partie 2.2.
### 2.3 Variables
On souhaite ajouter la possibiliter d'utiliser des variables dans les expressions. On a besoin pour cela de deux choses: un cas supplémentaire dans le type des expressions pour pourvoir y faire apparaître une variable et un moyen de récupérer la valeur d'une variable lors de l'évaluation de l'expression.
#### 2.3.1 Variables prédéfinies
On souhaite ajouter la possibilité d'utiliser des variables dans les expressions. On a besoin pour cela de deux choses: un cas supplémentaire dans le type des expressions pour pourvoir y faire apparaître une variable et un moyen de récupérer la valeur d'une variable lors de l'évaluation de l'expression.
Les variables sont représentées par leur nom dans les expressions:
......@@ -142,7 +154,7 @@ type expr =
Pour représenter la valeur des variables, on va utiliser des listes d'association (comme dans le TP2). On va cependant utiliser ici les fonctions fournies par la bibliothèque standard d'OCaml.
On rappele que le type OCaml `'a option` permet de représenter une valeur (avec le constructeur `Some x``x` est la valeur) ou bien l'absence de valeur avec le constructeur `None`.
On rappelle que le type OCaml `'a option` permet de représenter une valeur (avec le constructeur `Some x``x` est la valeur) ou bien l'absence de valeur avec le constructeur `None`.
La fonction [`List.assoc_opt`](https://v2.ocaml.org/api/List.html#1_Associationlists) permet de chercher la valeur associée à une clé dans une liste d'association, c'est-à-dire dans une liste de paires (clé,valeur). C'est la version généralisée de la fonction `cherche` du TP2.
......@@ -154,7 +166,7 @@ Pourquoi a-t-on ajouté le constructeur `VarNonDef` au type `eval_err` ?
> Modifier vos tests pour les ajuster aux changements précédents. Ajouter des tests pour les variables.
### 3.4 Let
### 2.3.2 Let
On va terminer en ajouter le constructeur `Let` aux expressions, comme `let var = expr1 in expr2` de OCaml. Ce constructeur va prendre un nom de variable (`string`) et **deux** expressions. La première est l'expression à évaluer pour obtenir la valeur de la variable. La deuxième expression sera utilisée pour calculer le résultat du `Let`. Ce résultat est simplement le résultat de cette expression évaluée avec les valeurs de variables utilisées pour évaluer le `Let` auxquelles on a ajouté la valeur de la variable du `Let`.
......@@ -167,8 +179,10 @@ type expr =
```
Par exemple pour évaluer l'expression `Let ("x", Cst 3, Binop (Plus, Cst 2, Var "x"))`, avec comme valeur de variables `[("y",42)]`, on évalue d'abord `Cst 3` avec les valeurs de variables `[("y",42)]`.
On obient alors comme résultat `Ok 3`.
On obtient alors comme résultat `Ok 3`.
On évalue alors `Binop (Plus, Cst 2, Var "x")` avec comme valeurs de variables `[("x", 3), ("y",42)]`.
On obient alors `Ok 5` qui sera le résultat de l'évaluation du `Let`.
On obtient alors `Ok 5` qui sera le résultat de l'évaluation du `Let`.
> Apporter les modifications nécessaires à la fonction `eval_expr` et ajouter les tests pour gérer le `Let`.
> Apporter les modification nécessaires à la fonction `eval_expr` et ajouter les tests pour gérer le `Let`.
Prévenez votre chargé de TP pour valider la partie 2.3.
# LIFPF TP4: Structures et fonctions génériques et mutuellement récursives
Lancez régulièrement tous vos tests via `ocaml tp4.ml` depuis le bash ou via `#use "tp4.ml";;` depuis l'interpréteur intéractif.
## 0. Retour sur le TP3
Si ce n'est pas fait, **terminer** le [TP3](tp3.md) jusqu'à la section 3.2.2 inclue.
Lancez régulièrement tous vos tests via `ocaml tp4.ml` depuis le bash ou via `#use "tp4.ml";;` depuis l'interpréteur interactif.
## 1. Arbres n-aires
......@@ -20,7 +16,7 @@ Un noeud contiendra donc simplement une forêt.
>
> Définir quelques arbres pour pouvoir tester.
>
> Donner deux exemples d'arbres ne contenant **aucun** éléments.
> Donner deux exemples d'arbres ne contenant **aucun** élément.
>
> Quel est le type inféré par OCaml pour ces arbres ? Pourquoi ?
......@@ -29,7 +25,10 @@ Un noeud contiendra donc simplement une forêt.
On souhaite maintenant pouvoir calculer la hauteur d'un arbre.
Comme un arbre peut contenir une forêt, il faut deux fonctions mutuellement récursives: une fonction qui donne la hauteur d'un arbre et une autre qui donne la hauteur maximale des arbres dans une forêt.
> Coder `hauteur_arbre` et `hauteur_foret`, deux fonctions mutuellement récursives qui calculent respectivement la hauteur d'un arbre et la hauteur maximale des arbres d'une forêt. Tester avec `assert`.
> Coder `hauteur_arbre` et `hauteur_foret`, deux fonctions mutuellement récursives qui calculent respectivement la hauteur d'un arbre et la hauteur maximale des arbres d'une forêt.
> Tester avec `assert`.
**Appeler votre chargé de TP pour faire valider votre progression**
### 1.3. Éléments d'un arbre
......@@ -42,11 +41,13 @@ Comme un arbre peut contenir une forêt, il faut **deux fonctions mutuellement r
Par ailleurs on veut éviter de faire des concaténations qui vont s'avérer coûteuses, on va donc écrire ces fonctions en **s'appuyant sur un accumulateur**.
Les fonctions vont ainsi ajouter les éléments de l'arbre/de la forêt à l'accumulateur.
> Coder deux fonctions mutuellement récursives `list_of_arbre_aux` et `list_of_foret` qui vont prendre un arbre / une forêt en argument, ainsi qu'une liste d'éléments `acc` qui vont ajouter en tête de `acc` les éléments de l'arbre / de la forêt. Vous pouvez aussi commencer par écrire une version **sans** accumulateur et la modifier ensuite.
> Coder deux fonctions mutuellement récursives `list_of_arbre_aux` et `list_of_foret` qui vont prendre un arbre / une forêt en argument, ainsi qu'une liste d'éléments `acc` qui vont ajouter en tête de `acc` les éléments de l'arbre / de la forêt.
> Vous pouvez aussi commencer par écrire une version **sans** accumulateur et la modifier ensuite.
>
> Tester avec `assert`.
>
> Placer la définition des deux fonctions à l'intérieur d'une fonction `list_of_arbre` (via un `let ... in ...`). Le code de `list_of_arbre` fera ensuite simplement appel à `list_of_arbre_aux` avec un accumulateur initial vide.
> Placer la définition des deux fonctions à l'intérieur d'une fonction `list_of_arbre` (via un `let ... in ...`).
> Le code de `list_of_arbre` fera ensuite simplement appel à `list_of_arbre_aux` avec un accumulateur initial vide.
>
> Modifier les tests précédents pour tester `list_of_arbre` et pas `list_of_arbre_aux` (qui est maintenant masquée).
......@@ -56,10 +57,15 @@ On veut maintenant extraire _l'élément minimal d'un arbre_.
Attention, si l'arbre est vide, le minimal n'est **pas** défini, le résultat sera donc un `'a option`.
Il faudra prendre en compte cette spécificité lors du traitement des appels récursifs, typiquement en faisant un pattern matching sur le résultat de l'appel récursif.
Remarque : la fonction `min` est prédéfinie en OCaml. Son type est `'a -> 'a -> 'a`. On l'utilisera pour obtenir le minimum de deux éléments de l'arbre.
On évitera cependant de l'utiliser directement avec des `option` car elle ne donnera pas le résultat voulu. Par exemple, `min None (Some 3)` vaut `None`.
**Remarque :** la fonction `min` est prédéfinie en OCaml. Son type est `'a -> 'a -> 'a`.
On l'utilisera pour obtenir le minimum de deux éléments de l'arbre.
On évitera cependant de l'utiliser directement avec des `option` car elle ne donnera pas le résultat voulu.
Par exemple, `min None (Some 3)` vaut `None`.
> Définir deux fonctions mutuellement récursives `minimum` et `minimum_foret` qui donne le minimum d'un arbre / d'une forêt.
> Tester avec `assert`.
> Définir deux fonctions mutuellement récursives `minimum` et `minimum_foret` qui donne le minimum d'un arbre / d'une forêt. Tester avec `assert`.
**Faire valider votre progression par votre chargé de TP**
### 1.5. Reduce
......@@ -67,15 +73,22 @@ On peut remarquer que dans le code de `minimum` et `minimum_foret`, la fonction
On pourrait donc généraliser ce code pour qu'il fonctionne avec **n'importe quelle fonction de combinaison des résultats**.
La fonction généralisée est souvent appelée `reduce`.
On veut donc coder les fonctions `reduce` et `reduce_foret` qui généralisent `minimum`. Elles prendront un argument supplémentaire `f` qui est la fonction de combinaison des résultats. Dans le code de ces fonctions, `f` sera utilisée à la place de `min`. Si l'arbre / la forêt contient des éléments de type `'a`, alors le type de `f` sera `'a -> 'a -> 'a`.
On veut donc coder les fonctions `reduce` et `reduce_foret` qui généralisent `minimum`.
Elles prendront un argument supplémentaire `f` qui est la fonction de combinaison des résultats.
Dans le code de ces fonctions, `f` sera utilisée à la place de `min`.
Si l'arbre / la forêt contient des éléments de type `'a`, alors le type de `f` sera `'a -> 'a -> 'a`.
> Coder `reduce` et `reduce_foret`. Tester en reprenant les cas de test utilisés pour `minimum`, en utilisant `min` comme valeur pour `f`.
> Coder `reduce` et `reduce_foret`.
> Tester en reprenant les cas de test utilisés pour `minimum`, en utilisant `min` comme valeur pour `f`.
Si ce n'est pas déjà fait, définir quelques exemples arbres dont le contenu sera de type `int`.
> Tester `reduce` et `reduce_foret` avec un fonction qui fait l'addition (en `int`) de ses deux arguments, ou le produit des éléments, le maximum etc.
**Remarque** : le type de l'addition est `int -> int -> int`. Cela fonctionne car le type de `reduce` est `('a -> 'a -> 'a) -> 'a arbre_n -> 'a option`, donc `reduce` a aussi le type `(int -> int -> int) -> int arbre_n -> int option`.
**Remarque** : le type de l'addition est `int -> int -> int`.
Cela fonctionne car le type de `reduce` est `('a -> 'a -> 'a) -> 'a arbre_n -> 'a option`, donc `reduce` a aussi le type `(int -> int -> int) -> int arbre_n -> int option`.
**Faire valider votre progression par votre chargé de TP**
## 2. FIFOs basées sur des listes
......@@ -84,14 +97,14 @@ Dans ce type de structure, on veut pouvoir ajouter et retirer des éléments de
Une implémentation naïve de FIFO basée sur une liste pose des problèmes de performance :
- soit on ajoute les nouveau éléments _en tête de liste_, mais il faut alors les retirer en fin de liste ce qui force à récrire toute la liste à chaque supression;
- soit in ajoute _en fin de liste_, mais ici c'est le coût à l'insertion qui est prohibitif puisqu'on reconstruit la liste au moment de l'ajout.
- soit on ajoute les nouveaux éléments _en tête de liste_, mais il faut alors les retirer en fin de liste ce qui force à récrire toute la liste à chaque supression;
- soit on ajoute _en fin de liste_, mais ici c'est le coût à l'insertion qui est prohibitif puisqu'on reconstruit la liste au moment de l'ajout.
Une autre technique consiste à utiliser **deux** listes.
La première (à gauche) est destinée à recevoir les nouveau éléments, alors que la seconde à droite sera utilisée pour stocker les éléments à retirer :
La première (à gauche) est destinée à recevoir les nouveaux éléments, alors que la seconde à droite sera utilisée pour stocker les éléments à retirer :
- dans la liste de gauche, les éléments sont stockés dans l'ordre **inverse** de leur insertion : le dernier élément inséré est en tête de liste.
- dans la liste de droite, les éléments sont stocké dans l'ordre où ils doivent être récupérés, c'est à dire **le plus ancien en tête de liste**.
- dans la liste de droite, les éléments sont stockés dans l'ordre où ils doivent être récupérés, c'est-à-dire **le plus ancien en tête de liste**.
Reste à transférer des éléments entre la liste de gauche et celle de droite.
C'est là que cette technique trouve son efficacité : on transfère de la gauche à la droite lorsque l'on essaie de retirer un élément de la liste de droite alors qu'elle est vide.
......@@ -204,11 +217,20 @@ flowchart LR
### 2.2 Type FIFO et ajout d'éléments
> Définir un type `'a fifo` avec un seul constructeur contenant deux listes dont les éléments sont de type `'a`. Définir quelques exemples d'éléments de ce type.
> Définir un type `'a fifo` avec un seul constructeur contenant deux listes dont les éléments sont de type `'a`.
> Définir quelques exemples d'éléments de ce type.
<!-- pour séparer les citations -->
> Définir une fonction `push_fifo` qui prend en argument un élément `e` et une fifo `f` et renvoie la fifo contenant les éléments de `f` puis `e` en insérant `e` en tête de la liste de gauche.
> Tester avec `assert` et les fifos exemples créées précédement.
> Définir une fonction `push_fifo` qui prend en argument un élément `e` et une fifo `f` et renvoie la fifo contenant les éléments de `f` puis `e` en insérant `e` en tête de la liste de gauche. Tester avec `assert` et les fifos exemples créées précédement.
<!-- pour séparer les citations -->
> Définir une fonction `push_list_fifo` qui prend une liste d'éléments `l` et une fifo `f` et renvoie la fifo contenant tous les éléments de `f` suivis de tous les éléments de `l` insérés dans l'ordre de `l` (i.e. l'élément en tête de `l` avant les autres). Tester avec `assert` et les fifos exemples créées précédement.
> Définir une fonction `push_list_fifo` qui prend une liste d'éléments `l` et une fifo `f` et renvoie la fifo contenant tous les éléments de `f` suivis de tous les éléments de `l` insérés dans l'ordre de `l` (i.e. l'élément en tête de `l` avant les autres).
> Tester avec `assert` et les fifos exemples créées précédement.
**Faire valider votre progression par votre chargé de TP**
### 2.3 Récupération des éléments et transfert
......@@ -224,4 +246,8 @@ Le résultat de `pop_fifo` sera une paire contenant d'une part la fifo sans l'é
> Coder la fonction `pop_fifo` et la tester avec `assert` et les fifos exemples créées précédement.
<!-- Séparateur de citation -->
> Coder la fonction `pop_all_fifo` qui prend une fifo `f` et renvoie la liste contenant tous les éléments dans l'ordre de `f`, c'est à dire qu'on veut le premier élément retiré de `f` en tête de la liste résultat. Tester avec `assert` et les fifos exemples créées précédement.
**Faire valider votre progression par votre chargé de TP**
......@@ -60,7 +60,7 @@ assert (list_of_arbre a6 = [ 1; 2; 1; 1; 2 ])
[minimum arbre] est le plus grand élément de arbre si arbre en contient au moins 1.
@param arbre l'arbre dans lequel on cherche le minimum
@return None si l'arbre ne contient pas d'élément, ou sinon Some m avec m le plus grand élément de l'arbre
@return None si l'arbre ne contient pas d'élément, ou sinon Some m avec m le plus petit élément de l'arbre
*)
let rec minimum (arbre : 'a arbre_n) : 'a option =
match arbre with
......@@ -71,7 +71,7 @@ let rec minimum (arbre : 'a arbre_n) : 'a option =
[minimum_foret l] donne l'élément minimal que l'on peut trouver dans une forêt
@param l la forêt
@return None si la forêt ne contient pas d'élément ou sinon Some m où m est le plus grand élément de la forêt
@return None si la forêt ne contient pas d'élément ou sinon Some m où m est le plus petit élément de la forêt
*)
and minimum_foret (la : 'a arbre_n list) : 'a option =
match la with
......
# TP5
# TP5: Parcours génériques de structure
## 1. Arbre n-aires: recodage
## 1. Manipulations de listes à l'aide de fonctions génériques
Dans cet exercice, on reprend les fonctionnalités développées dans la section 1. du [TP4](tp4.md), mais en les recodant avec les fonctions déjà fournies avec OCaml.
Remarque: en OCaml on peut écrire `fun x y -> ...` à la place de `fun x -> fun y -> ...`.
### 1.1. Recodage de quelques fonctions de base avec la bibliothèque standard OCaml
> Reprendre le type `'a arbre_n` du TP4, section 1.1
On pourra reprendre les tests écrits lors du TP4.
> Recoder la fonction `hauteur_arbre` sans utiliser `hauteur_foret`, mais en appelant directement `List.map` et `List.fold_left` pour extraire les hauteurs des arbres fils et trouver la hauteur maximale parmi celles-ci.
> Recoder `list_of_arbre_aux` en utilisant `List.fold_right` pour remplacer les appels à `list_of_foret`.
> Encapsuler `list_of_arbre_aux` avec un `let ... in ...` dans la définition de `list_of_arbre`.
> On écrira le `let` avant de prendre l'argument de `list_of_arbre`.
### 1.2 Gestion d'option, fold et minimum
Pour gérer proprement le calcul du minimum, on va s'équiper d'un décorateur `lift_option_2` ayant le comportement suivant. Soit `f: 'a -> 'a -> 'a`. On suppose que `g = lift_option_2 f`. Alors `g` aura le type `'a option -> 'a -> 'a option`. Si le premier argument de `g` est `None`, alors `g` renvoie `Some x` si `x` est son second argument. Sinon `g` renvoie `Some (f y x)``y` est la valeur contenue dans son premier argument et `x` est son second argument.
> Définir `lift_option_2` en commençant par préciser son type. Tester avec `assert` en passant la fonction `min` à `lift_option_2`.
On veut à présent définir `fold_left_arbre` qui aggrège une valeur via un accumulateur à la manière de `List.fold_left`, mais en parcourant un arbre et pas une liste.
On considère les fonctions suivantes prédéfinies en OCaml, voir [la documentation du module `List`](https://v2.ocaml.org/api/List.html) :
> Définir le type, puis coder `fold_left_arbre`. On pourra utiliser intelligement `List.fold_left` pour gérer le cas des `Noeud`.
> Tester en calculant la somme des éléments d'arbres bien choisis.
On peut remarquer que `reduce` du TP4 ressemble énormément à `fold_left_arbre`: les deux vont parcourir les éléments de l'arbre les combinant via un accumulateur. Elles diffèrent cependant sur les points suivants:
- `reduce` prend en argument une fonction de type `'a -> 'a -> 'a` alors que `fold_left_arbre` prend une fonction un peu plus générique de type `'b -> 'a -> 'b`.
- `reduce` renvoie forcément une `option` alors que `fold_left_arbre` renvoie uniquement une option si `'b` est lui-même une `option`.
> En utilisant intelligement `fold_left_arbre`, recoder `reduce`.
> Recoder `list_of_arbre` en utilisant `fold_left_arbre`.
## 2. Application et compilation séparée
- **`List.map: ('a -> 'b) -> 'a list -> 'b list`** Cette fonction transforme une liste en utilisant la fonction passée en paramètre pour transformer les éléments un par un. Ainsi `List.map f [x1; x2; x3; ... ]` renverra une liste égale à `[f x1; f x2; f x3; ... ]`.
- **`List.filter: ('a -> bool) -> 'a list -> 'a list`** Cette fonction transforme une liste en utilisant la fonction passée en paramètre conserver uniquement certains éléments. Ainsi `List.filter f [x1; x2; x3; ... ]` renverra une liste contenant exactement les `xi` pour lesquels `f xi` vaut `true`.
- **`List.for_all: ('a -> bool) -> 'a list -> bool`** Cette fonction indique si une fonction renvoie vrai pour tous les éléments d'une liste. Ainsi `List.for_all f [x1; x2; x3; ... ]` renvoie `true` si pour tous les `xi`, on a `f xi` vaut `true`.
On souhaite implémenter une application de gestion de la fabrication des jouets de Noël par les lutins du Père-Noël.
Bien que l'application soit modeste on souhaite pouyvoir la faire grossir selon les besoins.
On va donc dès le départ la diviser en différents modules, le code de l'application contenant simplement des appels aux bonnes fonctions des autres modules.
## 1.1. Utilisation des fonctions sur les listes
Les différentes parties de l'application intiale sont les suivantes:
- Un module `Association` de gestion des associations clé-valeur basé sur des arbres binaires de recherche. On veut pouvoir, à terme (mais pas dans ce TP), remplacer les usages de ce module par un module de la bibliothèque standard OCaml.
- Un module `Usine` contenant les types liés au métier de l'application: lutins, jouets, etc. Ce module contiendra également les différentes fonctions utilisées pour gérer l'usine.
- Un module `LutinsApp` qui va contenir le code de gestion des arguments en ligne de commande
On peut résumé les dépendances simples des modules de cette application via le diagrame suivant (la flèche signifie "est utilisé par"):
```mermaid
graph TD
Association --> Usine
Usine --> LutinsApp
LutinsApp --> main.ml
```
### 2.1. Mise en place d'un projet `dune`
**Remarque:** Si vous utilisez votre propre machine, il faut installer `opam` et `dune`. Voir la [documentation opam](https://opam.ocaml.org/doc/Install.html) et la [documentation dune](https://dune.build/install). Cette installation est déjà faite pour les machines des salles TP, mais il faut bien avoir effectué la configuration de votre compte ([cf doc](../CONFIGURATION.md)).
> Créer une nouveau projet dune intitulé `lutins` via la commande suivante:
> En utilisant `List.map`, coder les fonctions suivantes :
>
> ```
> dune init project lutins
> ```
La commande va créer un répertoire `lutins`. Ce répertoire contiendra un ou plusieurs sous-répertoires nommés `_build` qui vont contenir les défférents fichiers générés par les outils de compilation OCaml.
> - Une fonction `f1` qui prend une liste d'`int` et renvoie la liste de leur représentation sous forme de `string`.
> - Une fonction `f2` qui prend un `int` `x` et une liste d'`int` `l` et renvoie la liste transformée en multipliant chaque élément de `l` par `x`.
> - Une fonction `f3` qui prend deux `int` `x` et `y` et une liste d'`int` `l` et renvoie la liste (de `bool`) indiquant pour chacun des éléments de `l` s'il est bien compris entre `x` et `y`.
> Lister les fichiers et les répertoires générés par la commande `dune init`.
Bien tester vos fonctions avec `assert`.
> Lancer la commande `dune exec lutins` depuis le répertoire du projet. Dans quel fichier se trouve le code exécuté ?
> Ajouter le code suivant dans le fichier `test/lutins.ml`:
>
> ```ocaml
> assert (2=1)
> ```
> En utilisant `List.filter`, coder les fonctions suivantes :
>
> puis lancer la commande `dune test`.
> Constater l'erreur, puis supprimer cette ligne de test.
> - Une fonction `f4` qui prend deux `int` `x` et `y` et une liste d'`int` `l` et renvoie la liste des éléments de `l` qui sont compris entre `x` et `y`.
> - Une fonction `f5` qui prend une liste de liste d'`int` et renvoie la liste sans les (sous-)listes vides.
Pour finir créer à la racine du projet un fichier `.ocamlformat` avec le contenu suivant:
Bien tester vos fonctions avec `assert`.
```
profile = default
margin = 70
```
En OCaml, on peut voir un opérateur comme une fonction en le plaçant tout seul entre parenthèses. Ainsi `(!=)` est une fonction qui prend deux arguments et renvoie `true` s'ils sont différents. Ainsi `(!=) 1 2` renvoie `true`, alors que `(!=) 1 1` renvoie `false`.
Si vous avez installé `ocamlformat` (via `opam` sur votre machine, il sera préinstallé en salle TP), cela permettra de reformatter le code (c.-à-d. réarranger la présentation).
> Recoder la fonction `f5` sans le mot clé `fun` en utilisant une application partielle de `(!=)`.
### 2.2. Premier module
On peut bien sûr combiner `List.map` et `List.filter`.
Créer deux fichiers dans le répertoire `lib`. Le premier, nommé `usine.ml` contiendra le code du module `Usine`. Le second, `usine.mli` contiendra les déclarations de type et de fonction pour les modules et les fichiers qui utiliseront `Usine`.
> Coder une fonction `f6` qui prend une liste d'`int option` `l` et renvoie une liste d'`int` contenant les valeurs `int` contenues dans `l`. Par exemple, `f6 [ Some 3; None; Some 18; Some 42; None; Some 37; None]` vaudra `[3; 18; 42; 37]`.
> Dans le fichier `usine.ml`, définir un type somme `jour` pour représenter les jours de la semaine (un jour par constructeur).
> Définir également une fonction `string_of_jour` permettant d'obtenir la `string` représentant le jour passé en argument.
## 1.2 Recodage de fonctions sur les listes
Le fichier `usine.ml` va ainsi contenir l'**implémentation** du module `Usine`.
> Recoder `List.map`, `List.filter` et `List.for_all`.
On veut maintenant indiquer que ces deux déclarations sont disponibles aux autres modules. Pour cela ajouter le code suivant à l'autre fichier, c'est-à-dire `usine.mli`:
**Faire valider votre progression par votre chargé de TP**
```ocaml
(**
Les jours de la semaine.
*)
type jour = Lundi | Mardi
## 2. Arbre n-aires: recodage
(**
Donne une représentation sous forme de string d'un jour.
*)
val string_of_jour: jour -> string
```
Noter le commentaire de documentation, qui devient plus important ici car le code n'est pas accessible en dehors du module `Usine`.
`usine.mli` contient l'**interface** du module `Usine`.
### 2.3. Utilisation du module `Usine`
On souhaite maintenant utiliser ce module. Pour cela on va d'abord vérifier que la ligne suivante se trouve dans le fichier `bin/dune` (on l'ajoutera si elle est manquante):
```
(libraries lutins)
```
le contenu du fichier doit ressembler à ce qui suit:
```
(executable
(public_name lutins)
(name main)
(libraries lutins))
```
Le module `Usine` est maintenant disponible, pour utiliser ses types et ses fonctions depuis `main.ml`, il faudra les précéder de `Lutins.Usine`, par exemple pour utiliser le constructeur `Lundi`, on écrira `Lutins.Usine.Lundi`.
> Modifier le code de `main.ml` afin afficher en plus `Nous sommes ` suivi d'un jour de la semaine traduit en string via un appel à `string_of_jour`.
> Vérifier le bon fonctionnement du programme en lançant `dune exec lutins`. Dune va recompiler le programme et la bibliothèque (c'est-à-dire le code dans `lib` avant de lancer l'exécution du code).
On peut constater que l'usage de `Lutins.Usine` va vite devenir lourd.
Pour éviter ce problème, on peut utiliser la directive `open Lutins.Usine;;` en début de fichier. Le compilateur va ensuite directement chercher dans le module `Lutins.Usine` les définitions de `jour` et `string_of_jour` sans que l'on ait besoin de les précéder de `Lutins.Usine.`.
Enfin on veut pouvoir utiliser le système de test de dune avec le nouveau module `Lutins.Usine`. Pour cela, il suffit d'ajouter dans le fichier `test/dune` une déclaration `libraries` similaire à ce qui est fait dans `bin/dune`:
```
(test
(name lutins)
(libraries lutins))
```
Ensuite on peut utiliser, et donc tester, les types et les fonctions déclarées dans `usine.mli` comme cela a été fait dans le `main.ml`.
**Remarque:** Il se peut l'extension OCaml Platform de VSCode ait parfois du mal à se mettre à jour avec les nouvelles définitions. Si cela compile correctement avec `dune`, mais que VSCode indique des erreurs, c'est `dune` qui a raison.
> Ajouter des `assert` pour tester `string_of_jour`. Lancer `dune test` pour vérifier que tous vos tests passent correctement.
### 2.4. Masquer des définitions
On veut maintenant définir au sein de l'usine de fabrication de jouets une notion de _configuration_ de la journée. La configuration d'une journée permet de connaître deux choses: pour chaque lutin, le jouet qu'il va fabriquer et pour chaque lutin et chaque jouet la quantité qu'il peut fabriquer. Les jouets et les lutins étant représentés par des chaînes de caractères, il se peut qu'un lutin ou un jouet passé en argument n'existe pas. On utilise donc une `option` pour gérer cette situation.
On va maintenant créer dans le module `Usine` un type `configuration` qui sera simplement une paire de fonctions. La première aura le type `string -> string option`, la seconde aura le type `string -> string -> int option`.
Définir ce type dans `usine.ml`:
```ocaml
type configuration = (string -> option string) * (string -> string -> option int);;
```
Dans `usine.mli` on va en revanche, on va masquer les détails de la définition et juste conserver le nom du type:
```ocaml
type configuration;;
```
Tel quel, on ne peut pas manipuler les valeurs de ce type à l'extérieur du module `Usine`. On va donc lui ajouter des fonctions pour créer des configurations et en extraire les différentes parties.
Dans cet exercice, on reprend les fonctionnalités développées dans la section 1. du [TP4](tp4.md), mais en les recodant avec les fonctions déjà fournies avec OCaml.
Ajouter les fonctions suivantes, en les déclarant dans l'interface du module `Usine`
Remarque : en OCaml, on peut écrire `fun x y -> ...` à la place de `fun x -> fun y -> ...`.
- `mk_configuration: (string -> option string) -> (string -> string -> int option) -> configuration` cette fonction va fabriquer une paire avec ses deux arguments.
- `get_jouet: configuration -> (string -> string option)` cette fonction va extraire le premier élément de la paire
- `get_nb_jouets: configuration -> (string -> string -> int option)` cette fonction va extraire le deuxième élément de la paire
### 2.1. Recodage de quelques fonctions de base avec la bibliothèque standard OCaml
Tester ces fonctions dans `test/lutins.ml` avec une fonction qui renvoie toujours "toupie" pour le choix du jouet et toujours `42` pour le nombre de jouets.
> Reprendre le type `'a arbre_n` du TP4, section 1.1
### 2.5. Codage de l'application de gestion des jouets - début
On pourra reprendre les tests écrits lors du TP4.
On dispose à présent des éléments de langage pour pouvoir coder le début de l'application de gestion de l'usine de jouets.
> Recoder la fonction `hauteur_arbre` sans utiliser `hauteur_foret`, mais en appelant directement `List.map` et `List.fold_left` pour extraire les hauteurs des arbres fils et trouver la hauteur maximale parmi celles-ci.
Ajouter au module `Usine` une fonction `jour_of_string` qui prend une `string` et renvoie une option de `jour` avec `Some` du bon jour si la chaîne représente bien un jour et `None` sinon.
> Recoder `list_of_arbre_aux` en utilisant `List.fold_right` pour remplacer les appels à `list_of_foret`.
> Encapsuler `list_of_arbre_aux` avec un `let ... in ...` dans la définition de `list_of_arbre`.
> On écrira le `let` avant de prendre l'argument de `list_of_arbre`.
Créer un module `Association` contenant le code pour gérer des associations clé-valeur. Il contiendra:
**Faire valider votre progression par votre chargé de TP**
- un type générique `('a,'b) assoc_t` dont l'implémentation est cachée;
- une fonction `put: 'a -> 'b -> ('a, 'b) assoc_t -> ('a, 'b) assoc_t` qui associe une clé (de type `'a`) à une valeur (de type `'b`) dans une structure d'association;
- une fonction `get: 'a -> ('a, 'b) assoc_t -> 'b option` qui renvoie une valeur associée à une clé, si elle existe;
- une constante `empty: ('a,'b) assoc_t` correspondant à l'association vide.
### 2.2. Gestion d'option, fold et minimum
L'implémentation est laissée libre. On peut par exemple utiliser des listes de paires clé-valeur ou bien des arbres de recherche basés sur la comparaison générique `(<)` prédéfinie dans OCaml.
Pour gérer proprement le calcul du minimum, on va s'équiper d'un décorateur `lift_option_2` ayant le comportement suivant. Soit `f: 'a -> 'a -> 'a`. On suppose que `g = lift_option_2 f`. Alors `g` aura le type `'a option -> 'a -> 'a option`. Si le premier argument de `g` est `None`, alors `g` renvoie `Some x` si `x` est son second argument. Sinon `g` renvoie `Some (f y x)``y` est la valeur contenue dans son premier argument et `x` est son second argument.
Dans le module `Usine`, utiliser cette structure pour créer une variable globale pour y stocker, pour chaque jour, une configuration. Essayer de faire varier les fonctions de la configuration selon le jour.
> Définir `lift_option_2` en commençant par préciser son type. Tester avec `assert` en passant la fonction `min` à `lift_option_2`.
Créer également une variable globale contenant la liste des noms des lutins.
On veut à présent définir `fold_left_arbre` qui agrège une valeur via un accumulateur à la manière de `List.fold_left`, mais en parcourant un arbre et pas une liste.
Créer une fonction `calcule_jouets_config: configuration -> (string,int) list` qui indique pour chaque jouet combien d'exemplaires ont été fabriqué, en excluant les jouets fabriqués zéro fois.
> Définir le type, puis coder `fold_left_arbre`. On pourra utiliser intelligemment `List.fold_left` pour gérer le cas des `Noeud`.
> Tester en calculant la somme des éléments d'arbres bien choisis.
Enfin créer un dernier module `LutinsApp` qui contiendra:
On peut remarquer que `reduce` du TP4 ressemble énormément à `fold_left_arbre`: les deux vont parcourir les éléments de l'arbre les combinant via un accumulateur. Elles diffèrent cependant sur les points suivants:
- une fonction `affiche_jouets: (string,int) list -> string` qui calculera une chaîne d'affichage de la sortie de la fonction `calcule_jouets_config`;
- une fonction `run: string list -> unit` qui prendra une liste de string dont le premier élément représente un jour et affichera (via `print_endline`) les jouets produits ce jour-là.
- `reduce` prend en argument une fonction de type `'a -> 'a -> 'a` alors que `fold_left_arbre` prend une fonction un peu plus générique de type `'b -> 'a -> 'b`.
- `reduce` renvoie forcément une `option` alors que `fold_left_arbre` renvoie uniquement une option si `'b` est lui-même une `option`.
`main.ml` appelera `run` en lui passant la liste des arguments en ligne de commande (on pourra utiliser `Sys.argv` et `Array.to_list` pour récupérer les arguments et les transformer en `string list` pour être traités par `run`).
> En utilisant intelligemment `fold_left_arbre`, recoder `reduce`.
Il sera utile de consulter les modules de la biliothèque standard de OCaml ([lien pour la version 4](https://v2.ocaml.org/releases/4.14/api/index.html) utilisée en TP).
> Recoder `list_of_arbre` en utilisant `fold_left_arbre`.
Une version plus évoluée de cette application sera codée dans les TPs ultérieurs.
**Faire valider votre progression par votre chargé de TP**
(**********************************************************************)
(* 1 Manipulations de listes à l'aide de fonctions génériques *)
(**********************************************************************)
(**********************************************************************)
(* 1.1. Utilisation des fonctions sur les listes *)
(** Une fonction `f1` qui prend une liste d'`int` et renvoie la liste de leur représentation sous forme de `string`. *)
let f1 (l : int list) : string list = List.map (fun x -> string_of_int x) l
(* ou bien *)
let f1 = List.map string_of_int;;
assert (f1 [ 1; 2 ] = [ "1"; "2" ])
(** Une fonction `f2` qui prend un `int` `x` et une liste d'`int` `l` et renvoie la liste transformée en multipliant chaque élément de `l` par `x`. *)
let f2 (x : int) (l : int list) : int list = List.map (fun y -> x * y) l
(* ou bien *)
let f2 (x : int) : int list -> int list = List.map (fun e -> x * e);;
assert (f2 3 [ 1; 5; 3 ] = [ 3; 15; 9 ])
(** Une fonction `f3` qui prend deux `int` `x` et `y` et une liste d'`int` `l` et renvoie la liste (de `bool`) indiquant pour chacun des éléments de `l` s'il est bien compris entre `x` et `y`. *)
let f3 (x : int) (y : int) (l : int list) : bool list =
List.map (fun e -> x <= e && e <= y) l
(* ou bien *)
let f3 (x : int) (y : int) : int list -> bool list =
List.map (fun e -> x <= e && e <= y)
;;
assert (f3 3 7 [ 1; 4; 2; 12 ] = [ false; true; false; false ])
(** Une fonction `f4` qui prend deux `int` `x` et `y` et une liste d'`int` `l` et renvoie la liste des éléments de `l` qui sont compris entre `x` et `y`. *)
let f4 (x : int) (y : int) (l : int list) : int list =
List.filter (fun e -> x <= e && e <= y) l
(* ou bien *)
let f4 (x : int) (y : int) : int list -> int list =
List.filter (fun e -> x <= e && e <= y)
;;
assert (f4 3 7 [ 1; 4; 2; 12 ] = [ 4 ])
(** Une fonction `f5` qui prend une liste de liste d'`int` et renvoie la liste sans les (sous-)listes vides. *)
let f5 (l : int list list) : int list list = List.filter (fun l' -> l' != []) l
(* variante avec (=) *)
let f5 : int list list -> int list list = List.filter (( != ) []);;
assert (f5 [ []; [ 2; 3 ]; []; [ 1 ] ] = [ [ 2; 3 ]; [ 1 ] ])
(** Coder une fonction `f6` qui prend une liste d'`int option` `l` et renvoie une liste d'`int` contenant les valeurs `int` contenues dans `l`. Par exemple, `f6 [ Some 3; None; Some 18; Some 42; None; Some 37; None]` vaudra `[3; 18; 42; 37]`. *)
let f6 (l : int option list) : int list =
let l1 = List.filter (fun o -> o != None) l in
List.map
(fun o ->
match o with
| None -> 0 (* n'arrive pas à cause du filter précédent *)
| Some x -> x)
l1
(* ou bien *)
let f6 (l : int option list) : int list =
l
|> List.filter (( != ) None)
|> List.map (fun o ->
match o with
| None -> 0 (* n'arrive pas à cause du filter précédent *)
| Some x -> x)
(* ou encore en utilisant Option.get *)
let f6 (l : int option list) : int list =
l |> List.filter (( != ) None) |> List.map Option.get
;;
assert (
f6 [ Some 3; None; Some 18; Some 42; None; Some 37; None ] = [ 3; 18; 42; 37 ])
(**********************************************************************)
(* 1.2. Recodage de fonctions sur les listes *)
let rec map (f : 'a -> 'b) (l : 'a list) : 'b list =
match l with [] -> [] | x :: l' -> f x :: map f l'
let rec filter (f : 'a -> bool) (l : 'a list) : 'a list =
match l with
| [] -> []
| x :: l' -> if f x then x :: filter f l' else filter f l'
let rec for_all (f : 'a -> bool) (l : 'a list) : bool =
match l with [] -> true | x :: l' -> f x && for_all f l'
(**********************************************************************)
(* 2 Arbre n-aires: recodage *)
(**********************************************************************)
(**********************************************************************)
(* 2.1. Recodage de quelques fonctions de base avec la bibliothèque
standard OCaml *)
type 'a arbre_n = Feuille of 'a | Noeud of 'a arbre_n list
(**
Calcule la hauteur d'un arbre.
@param a l'arbre dont on veut calculer la hauteur
@return la hauteur de l'arbre
*)
let rec hauteur_arbre (a : 'a arbre_n) : int =
match a with
| Feuille _ -> 1
| Noeud foret ->
let hauteurs = List.map hauteur_arbre foret in
let h_max = List.fold_left max 0 hauteurs in
h_max + 1
(**
Extrait les éléments d'un arbre dans une liste
@param l'arbre dont on veut les éléments
@return la liste des éléments de l'arbre obtenue par un parcours en profondeur.
*)
let list_of_arbre : 'a arbre_n -> 'a list =
(*
Ajoute les éléments de l'arbre à la liste.
@param a l'arbre dont on veut extraires les éléments
@param acc la liste à laquelle on veut ajouter les éléments.
*)
let rec list_of_arbre_aux (a : 'a arbre_n) (acc : 'a list) : 'a list =
match a with
| Feuille x -> x :: acc
| Noeud foret ->
List.fold_left (fun acc2 fils -> list_of_arbre_aux fils acc2) acc foret
in
fun a -> list_of_arbre_aux a []
(**********************************************************************)
(* 2.2. Gestion d'option, fold et minimum *)
(**
[lift_option_2 f] choisi son deuxième argument si son premier argument est None.
Sinon utilise f pour produire une valeur avec ses deux arguments.
@param f la fonction utilisée pour combiner les arguments
@param x une option
@param y la valeur à combiner à la valeur de x ou à prendre si x est None
@return Some de la combinaison des valeurs de x et y, ou bien y si x est None
*)
let lift_option_2 (f : 'a -> 'a -> 'a) : 'a option -> 'a -> 'a option =
fun x y -> match x with None -> Some y | Some x' -> Some (f x' y)
(**
aggrège une valeur en utilisant un accumulateur et une fonction appelée pour
mettre à jour l'accumulateur en fonction de l'élément de l'arbre rencontrée.
Appelle la fonction en utilisant les un après les autres tous les éléments de
l'accumulateur.
@param f la fonction de mise à jour de l'accumulateur
@param init la valeur de départ de l'accumulateur
@param a l'arbre à parcourir
@return la valeur de l'accumulateur résultant des mises à jour
successives par les appels à f sur les éléments de a.
*)
let rec fold_left_arbre (f : 'b -> 'a -> 'b) (init : 'b) (a : 'a arbre_n) : 'b =
match a with
| Feuille x -> f init x
| Noeud foret -> List.fold_left (fold_left_arbre f) init foret
(* Pour le dernier cas on aurait pu écrire
List.fold_left (fun acc fils -> fold_left_arbre f acc fils) init foret
mais c'est plus long
*)
(**
Aggrège une valeur en utilisant une fonction de combinaison de valeurs
@param f la fonction de combinaison de valeurs
@param a l'arbre dont on veut combiner les valeurs
@return Some du résultat de la combinaisons des valeurs de a par f,
ou None si a n'a pas d'élément
*)
let reduce (f : 'a -> 'a -> 'a) (a : 'a arbre_n) : 'a option =
fold_left_arbre (lift_option_2 f) None a
(**
Extrait les éléments d'un arbre dans une liste
@param l'arbre dont on veut les éléments
@return la liste des éléments de l'arbre obtenue par un parcours en profondeur.
*)
let list_of_arbre' : 'a arbre_n -> 'a list =
fold_left_arbre (fun l e -> e :: l) []
# TP6 : Parcours et transformations de structures
**Attention** dans les sujets qui suivent, il est demandé de ne **pas regarder le corrigé** pour faire les exercices.
Lire un corrigé et (re)faire l'exercice seul (sans regarder une correction) sont deux choses très différentes.
**Votre chargé de TP validera votre progression en fin de séance.**
## 1. Manipulations de listes
Coder l'exercice 1 du [TD5](../td/lifpf-td5-enonce.pdf), en faisant en priorité ce qui n'a pas pu être fait en séance de TD.
## 2. Parcours et transformation d'arbres de recherche
Coder l'exercice 3 du [TD4](../td/lifpf-td4-enonce.pdf)
## 3. Calculatrice
Terminer complètement le codage de la calculatrice du [TP3](tp3.md).
# TP7 - Application et configuration par fonctions
On va implémenter une petite application simple en OCaml.
Même si l'application tiendra en un seul fichier, elle comprendra plusieurs parties regroupées en _modules_.
## 1. Application et modules
On souhaite implémenter une application de gestion de la fabrication des jouets de Noël par les lutins du Père-Noël.
Bien que l'application soit modeste on souhaite pouvoir la faire grossir selon les besoins.
On va donc dès le départ la diviser en différents modules, le code de l'application contenant simplement des appels aux bonnes fonctions des autres modules.
Les différentes parties de l'application intiale sont les suivantes:
- Un module `Association` de gestion des associations clé-valeur basé d'abord sur des listes, puis sur des arbres binaires de recherche. On veut pouvoir, à terme (mais pas dans ce TP), remplacer les usages de ce module par un module de la bibliothèque standard OCaml.
- Un module `Usine` contenant les types liés au métier de l'application: lutins, jouets, etc. Ce module contiendra également les différentes fonctions utilisées pour gérer l'usine.
- Un module `LutinsApp` qui va contenir le code de gestion des arguments en ligne de commande
- Enfin le code hors de ces modules lancera simplement l'application.
Tous ces modules seront placés dans le fichier `usinelutins.ml`.
On peut résumer les dépendances simples des modules de cette application via le diagramme suivant (la flèche signifie "est utilisé par"):
```mermaid
graph TD
A[Association] --> B[Usine]
B --> C[LutinsApp]
C --> D[code directement dans usinelutins.ml]
```
### 2. Premiers modules
Créer un fichier `usinelutins.ml`. À titre d'exemple de module on donne le code suivant, à placer au début du fichier `usinelutins.ml`:
```ocaml
module Association = struct
(* Type pour représenter des dictionnaires (ou map ou association) dont la clé est une string *)
type 'a t = (string * 'a) list
(* Fonction pour chercher dans un dictionnaire. get d k renvoie None si k n'est pas une clé de d,
Some x si x est la valeur associée à k dans d. *)
let get : 'a t -> string -> 'a option = fun d k -> List.assoc_opt k d
(* Ajoute un couple clé/valeur dans un dictionnaire, si la clé est déjà dans le dictionnaire,
change en v la valeur qui sera renvoyée par get *)
let put : 'a t -> string -> 'a -> 'a t = fun d k v -> (k, v) :: d
(* Le dictionnaire vide *)
let empty : 'a t = []
(* Supprime les valeurs associées à la clé k dans le dictionnaire. *)
let rec delete : 'a t -> string -> 'a t =
fun d k ->
match d with
| [] -> []
| (k', v') :: rest ->
if k = k' then delete rest k else (k', v') :: delete rest k
end
```
Créer un module `Usine` à la suite du module `Association`. Dans ce module, créer:
- un type `jour` pour représenter les jours de la semaine (`Lundi`, `Mardi`, etc)
- une fonction `string_of_jour: jour -> string`
- une fonction `jour_opt_of_string: string -> jour option`
> Ajouter des `assert` pour tester `string_of_jour` et `jour_opt_of_string`.
### 3. Utilisation du module `Usine`
On souhaite maintenant utiliser ce module. Pour cela il faut:
1. Écrire le code qui l'utilise après la définition du module
2. Faire précéder le nom d'une fonction, d'un type ou d'un constructeur par le nom du module, par exemple on écrira `Usine.Lundi`.
Ajouter le code suivant à la fin de `usinelutins.ml`:
```ocaml
let usage () = print_endline "\nUsage: ocaml usinelutins.ml jour"
let run args =
match args with
| _pgm :: jour_s :: _ -> print_endline "Bonjour, nous sommes un certain jour"
| _ -> usage ()
let _ = run (Array.to_list Sys.argv)
```
> Modifier ce code afin afficher en plus `Nous sommes ` suivi d'un jour de la semaine traduit en string via un appel à `string_of_jour`.
> Vérifier le bon fonctionnement du programme en lançant `ocaml usinelutins.ml`. Modifier le code de façon à ce que le jour choisi soit obtenu à partir de `jour_s`. Gérer par un affichage approprié le cas ou `jour_s` n'est pas un jour.
### 4. Enrichir le module `Usine`
On veut maintenant définir au sein de l'usine de fabrication de jouets une notion de _configuration_ de la journée. La configuration d'une journée permet de connaître deux choses: pour chaque lutin, le jouet qu'il va fabriquer et pour chaque lutin et chaque jouet la quantité qu'il peut fabriquer. Les jouets et les lutins étant représentés par des chaînes de caractères, il se peut qu'un lutin ou un jouet passé en argument n'existe pas. On utilise donc une `option` pour gérer cette situation.
On va maintenant créer dans le module `Usine` un type `configuration` qui sera simplement une paire de fonctions. La première aura le type `string -> string option`, la seconde aura le type `string -> string -> int option`.
Définir ce type le module `Usine`:
```ocaml
type configuration = (string -> string option) * (string -> string -> int option);;
```
Ajouter les fonctions suivantes dans le module `Usine`:
- `mk_configuration: (string -> string option) -> (string -> string -> int option) -> configuration` cette fonction va fabriquer une paire avec ses deux arguments.
- `get_jouet: configuration -> (string -> string option)` cette fonction va extraire le premier élément de la paire
- `get_nb_jouets: configuration -> (string -> string -> int option)` cette fonction va extraire le deuxième élément de la paire
Tester ces fonctions avec des `assert` en utilisant une fonction qui renvoie toujours "toupie" pour le choix du jouet et toujours `42` pour le nombre de jouets.
### 5. Codage de l'application de gestion des jouets - début
On dispose à présent des éléments de langage pour pouvoir coder le début de l'application de gestion de l'usine de jouets.
Dans le module `Usine`, créer une configuration basée sur des `assoc` pour créer une variable globale pour y stocker, pour chaque jour, une configuration. Essayer de faire varier les fonctions de la configuration selon le jour.
Créer également une variable globale contenant la liste des noms des lutins.
Créer une fonction `calcule_jouets_config: configuration -> (string,int) list` qui indique pour chaque jouet combien d'exemplaires ont été fabriqué, en excluant les jouets fabriqués zéro fois.
Enfin créer un dernier module `LutinsApp` qui contiendra:
- une fonction `affiche_jouets: (string * int) list -> string` qui calculera une chaîne d'affichage de la sortie de la fonction `calcule_jouets_config`;
Modifier la fonction `run: string list -> unit` pour affichera (via `print_endline`) les jouets produits durant le jour passé en argument.
Avec l'implémentation actuelle, il est possible que certains jouets s'affichent plusieurs fois, cela sera corrigé avec la section suivante.
### 6. Optimisation du module Association
Changer le type et le code du module `Association` de façon à utiliser des arbres binaires de recherche à la place de listes de paires. Le code du reste de l'application ne devrait pas changer.