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
Showing with 2102 additions and 0 deletions
files/vscode-1-src-ctl-view.png

29.4 KiB

# Rappels sur l'utilisation de git et Gitlab
## Site forge.univ-lyon1.fr
Le site https://forge.univ-lyon1.fr héberge une forge logicielle (Gitlab) qui permet d'héberger des projets, par exemple les TPs de LIFPF.
Il est possible de donner des droits à d'autres utilisateurs, par exemple pour travailler en binôme (dans un projet depuis le menu "Project information > Members").
## Créer un nouveau projet
Un fois connecté(e), il est possible de créer un nouveau projet (bouton bleu à droite).
![gitlab-1-new-project](files/gitlab-1-new-project.png)
Choisir de créer un projet vierge (blank project).
![gitlab-2-blank-project](files/gitlab-2-blank-project.png)
Donner un nom au projet, choisir un répertoire parent (typiquement votre nom d'utilisateur) et laisser cochée la case du README, puis lancer la création via le bouton "Create project".
![gitlab-3-cfg-project](files/gitlab-3-cfg-project.png)
## Récupérer un projet sur sa machine (clone)
Depuis le projet dans le navigateur, cliquer sur le menu du bouton "Clone" et copier l'URL de clone HTTPS.
![gitlab-4-clone-url](files/gitlab-4-clone-url.png)
Depuis le répertoire où on veut placer le dossier du projet, lancer la commande suivante en remplaçant `<url_du_projet>` par la valeur copiée depuis le navigateur:
```shell
git clone <url_du_projet>
```
Cela créée un répertoire ayant le nom du projet.
Certaines commandes `git`, en particulier `clone`, `pull`, `push` et `fetch`, demanderont probablement un login et un mot de passe. Ce sont ceux de votre compte Lyon 1.
## Synchroniser son travail
Remarque: lors de l'utilisation de VSCode, il est conseillé d'ouvrir le répertoire complet du projet et pas seulement un fichier (via la commande `code nom_du_répertoire` ou via le menu "_File > Open folder_").
Avant de commencer à travailler lancer la commande suivante dans le répertoire du projet:
```shell
git pull
```
Cette commande va répcupérer les dernière mise à jour de votre projet et les appliquer dans le répertoire du projet.
On peut alors commencer à travailler sur les fichiers.
Une fois que le travail stabilisé (par exemple à la fin d'une partie d'un TP), on peut _comiter_ les modifications.
Pour cela, on commence par identifier les fichiers à _comiter_, via la commande `git add`. On peut faire autant de git add que l'on souhaite. La commande git status permet de lister les modifications par rapport à la dernière version _comitée_ par exemple:
```shell
❯ git status
On branch main
Your branch is up to date with 'origin/main'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
titi.ml
toto.ml
tutu.ml
nothing added to commit but untracked files present (use "git add" to track)
❯ git add titi.ml
❯ git add toto.ml tutu.ml
❯ git status
On branch main
Your branch is up to date with 'origin/main'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: titi.ml
new file: toto.ml
new file: tutu.ml
```
Une fois les modifications ajoutée, on peut lancer la commande `git commit` en indiquant un message:
```shell
❯ git commit -m "tp7: partie 1.2 terminée"
[main bfec856] tp7: partie 1.2 terminée
3 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 titi.ml
create mode 100644 toto.ml
create mode 100644 tutu.ml
```
**Attention**, les modification ne sont pas encore répercutées sur forge.univ-lyon1.fr. Pour le faire, il faut utiliser `git push`:
```shell
❯ git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 10 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 314 bytes | 314.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://forge.univ-lyon1.fr/EMMANUEL.COQUERY/lifpf-tp7.git
17d2e59..bfec856 main -> main
```
Remarque: VSCode intègre des outils graphiques (vue latérale) pour gérer les opérations `add`, `commit`, `pull` et `push`. Il permet également de voir les modifications apportées au fichiers et facilite la résolution des conflits (cf ci-dessous).
![vscode-1-src-ctl-view](files/vscode-1-src-ctl-view.png)
### Conflits
Lors d'un travail à plusieurs, il est possible que le dépôt ait été édité entre le premier `pull` et le `push`. Dans ce cas `git` refusera de faire le `push`. Il faut alors faire un `pull` pour intégrer les modifications distantes avant de faire le `push`.
Si `git` ne parvient pas à fusionner les modifications, il se peut qu'il génère des conflits, visibles via `git status`et dans VSCode (lettre C à côté du fichier).
Dans ce cas, il faut éditer le fichier et rectifier à la main son contenu.
On peut ouvrir la vue latérale `git` dans VSCode et cliquer sur le fichier pour voir les conflits. Pour chaque conflit, VSCode proposera de prendre une des deux version, voir les deux. Souvent, cela ne dispense pas d'aller éditer le code pour le rectifier.
Une fois les fichiers éditer, on les ajoute comme pour une modification usuelle.
Il suffit ensuite de _comiter_ et pousser (`push`) les modifications.
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
# TP1: Découverte du langage OCaml
## 0. Environnement de travail
Les TPs s'effectueront sous Linux.
Vérifier que `ocaml` est disponible.
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]`.
## Partie 1
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
utop
```
ou simplement
```bash
ocaml
```
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.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 `;;`.
> Saisir quelques opérations sur les entiers, par exemple:
>
> ```ocaml
> 3*2+5/2;;
> ```
On remarque qu'en plus du résultat, l'interpréteur nous indique son type, ici `int`.
On peut également faire des opérations sur des nombres flottants, mais il faut
ajouter aux opérateur un `.`. Par exemple, pour OCaml `+` ne fonctionne que pour
les `int` et c'est `+.` qu'il faut utiliser pour additioner des `float`.
> Saisir quelques opérations arithmétiques sur des `float`.
OCaml possède également un type `string` (délimitées par des doubles quotes, par exemple `"abcd"`). On peut concaténer des chaînes avec l'opérateur `^`).
> Saisir quelques chaînes de caractères assemblées par concaténation.
Il est évidement possible d'utiliser les booléens en OCaml. Les constantes sont `true` et `false` et les opérateurs sont `&&` et `||`.
> É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 signe `=`), `!=`, `>`, `<`, `>=`, `<=`. Ils permettent de comparer des valeurs de même type.
> Expliquer la différence de comportement entre ces deux expressions:
>
> ```ocaml
> 3 < 2;;
> ```
>
> et
>
> ```ocaml
> 3 < 2.0;;
> ```
#### 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)
passés à la fonction. Cette syntaxe ressemble à celle d'une commande invoquée
avec des arguments dans un shell Unix. Elle peut également rappeler la syntaxe
d'appel des fonctions en Scheme, mais sans les parenthèses. Il sera cependant
parfois nécessaire de placer des parenthèses autour d'un argument si celui-ci
est lui-même un appel de fonction, par exemple dans l'expression `f (f2 a1) a2 a3`,
le premier argument `f2 a1` est placé entre parenthèses.
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.
Les opérateurs des expression arithmétiques sont moins prioritaires que les appels de fonction.
Par exemple `int_of_float 3.5 + 2` doit être lu comme `(int_of_float 3.5) + 2`.
> Écrire une expression combinant des opérateurs arithmétiques et ces deux fonctions.
La négation dans les booléens n'est pas un opérateur, mais simplement la fonction `not`.
> 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`.
> Récrire l'expression suivante en utilisant les opérateurs comme des fonctions:
>
> ```ocaml
> 2 < 3 && "b" < "ab"
> ```
#### 1.1.3. Variables
Pour rappel, la syntaxe des définitions de variables est :
```ocaml
let ma_variable = sa_valeur in expression_qui_utilise_ma_variable
```
par exemple, saisir dans l'interpréteur:
```ocaml
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 :
```ocaml
let x1 = 3.5 +. float_of_int 2 in
let x2 = x1 *. x1 in
x2 *. 2.0 ;;
```
#### 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.
> **Prédire** puis vérifier le résultat de l'expression suivante:
>
> ```ocaml
> (if true then 3 else 5) > 4
> ```
### 1.2. Définition de fonctions
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
let f a1 a2 a3 = valeur_de_retour;;
```
Par exemple une fonction `f` définie en $\lambda$-calcul par $\lambda x.(x+1)$ peut se définir en ocaml par:
```ocaml
let f x = x + 1;;
```
> Définir cette fonction dans `tp1.ml` puis l'utiliser dans une expression.
>
> Définir une fonction `discriminant` qui prend les trois coefficients `float` d'un trinôme $ax^2+bx+c$ et en calcule le discriminant ($b^2-4ac$).
>
> 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 :
```ocaml
let f (a1:t1) (a2:t2): type_retour = valeur_de_retour;;
```
> Récrire la fonction `discriminant` en forçant le type des arguments à être des `float`.
> Tester que cette nouvelle définition marche dans l'interpréteur par exemple avec 2.0 8.0 et 8.0 comme valeurs de a, b et c.
Récrire une deuxième fois cette fonction en changeant le type de `a` en `int` sans changer le reste de la fonction. Expliquer l'erreur produite par l'interpréteur.
> 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.
Voici une exemple de documentation à placer juste avant la définition de discriminant:
```ocaml
(**
[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
*)
```
**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.
### 1.3. 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 :
```ocaml
type couleur = Rouge | Jaune | Bleu;;
```
Ici on définit un type `couleur` avec trois valeurs possibles (`Rouge`, `Jaune` et `Bleu`).
> Entrer les expressions suivantes dans l'interpréteur et expliquer ce qu'il affiche:
>
> ```ocaml
> Rouge;;
> Rouge = Rouge;;
> Rouge != Bleu;;
> Bleu > Jaune;;
> ```
`Rouge`, `Jaune` et `Bleu` sont appelés les _constructeurs_ du type `couleur`.
> Ajouter les couleurs Violet, Orange et Vert aux couleurs possibles en redéfinissant le type couleur.
Il est possible d'avoir des valeur plus riches en leur associant une ou plusieurs valeurs d'autres types. Par exemple on peut écrire la définition suivante:
```ocaml
type mes_valeurs =
UnInt of int
| DeuxInt of int * int
| UneChaine of string
;;
```
Les constructeurs prennent ici des arguments, par exemple on peut fabriquer des valeurs de type `mes_valeurs` comme suit:
```ocaml
UnInt 3;;
UneChaine "toto";;
DeuxInt (3,4);;
```
> Ajouter un constructeur supplémentaire `RJB` au type couleur. Ce constructeur devra prendre 3 `int` en argument.
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)` ?
#### 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:
```ocaml
match c with
| Rouge -> "rouge"
| Bleu -> "bleu"
| Jaune -> "jaune"
| _ -> "mélange"
```
s'évalue en `"rouge"` si `c` contient `Rouge` ou encore sur `"mélange"` si `c` n'est ni `Rouge`, ni `Jaune`, ni `Bleu`.
> 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.
Par exemple on peut définir la fonction suivante qui donne le "niveau de rouge" d'une couleur (entre 0 et 255):
```ocaml
let niveau_rouge c =
match c with
| Rouge -> 255
| Orange -> 255
| Violet -> 255
| RJB (r,j,b) -> r
| _ -> 0;;
```
Ici, dans le cas où la couleur est définie via `RJB` la valeur dur premier argument est placée dans une variable `r` qui est réutilisée dans le résultat.
Le pattern matching peut aussi s'utiliser avec les types prédéfinis comme `int`, `string`, etc.
On veut implémenter un générateur pour les paroles de la chanson [99 bottles of beer](https://www.99-bottles-of-beer.net/lyrics.html). Pour cela on va commencer par générer un paragraphe en fonction du nombre de bouteilles de bières disponible. Regarder les paroles disponibles depuis la page de la chanson, en particulier les différences entre les 4 derniers paragraphes.
> Écrire une fonction `paragraphe_bottles` qui prend en argument un `int` et
> renvoie le paragraphe commençant par ce nombre de bouteilles. On aura besoin
> de la concaténation de `string` notée `^` et de la conversion d'`int` en
> `string`, notée `string_of_int`. `"\n"` permet de créer une nouvelle ligne. On
> utilisera `match` pour distinguer les cas 0, 1 ou 2 bouteilles du cas général
> à _n_ bouteilles.
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`:
```ocaml
(**
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);;
```
> Écrire une fonction `factorielle` qui calcule le produit des entiers entre 1 et _n_.
### 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
associatif à droite et il est également possible d'utiliser la notation `[ x1; x2; ... ]`
(attention au `;` et pas `,`) pour représenter la liste contenant les élements
x1, x2, etc. Ainsi la liste contenant les `int` `1`, `2`, `3`, `5`, `7` peut
s'écrire de différentes manières comme par exemple:
```ocaml
1 :: (2 :: (3 :: (5 :: (7 :: []))))
1 :: 2 :: 3 :: 5 :: 7 :: []
[1; 2; 3; 5; 7]
```
Par ailleurs les listes OCaml doivent contenir des élements qui sont tous du même type.
Une liste contenant des `int` aura ainsi le type `int list`, une liste de `string` aura le type `string list`
> Expliquer l'erreur produite par l'expression `"2" :: 3 :: []`
Le pattern matching fonctionne sur les listes avec les notation introduites
ci-dessus. On peut par exemple écrire une fonction récursive pour calculer le
nombre d'éléments d'une liste d'`int`:
```ocaml
(**
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
;;
```
> 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 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
> concaténés les un aux autres, mais en insérant le séparateur entre deux
> éléments. Attention, le séparateur ne doit pas apparaître avant le premier
> élément, ni après le dernier.
On peut aussi utiliser une fonction récursive pour fabriquer une liste
> Écrire une fonction `liste_n_0` qui prend en argument un `int` `n` et renvoie
> la liste contenant les `int` décroissants de `n` à `0` inclus.
On peut bien sûr faire les deux à la fois dans une même fonction: décomposer une
valeur de liste par cas avec un `match` et fabriquer une liste comme résultat.
> Écrire une fonction `bottles_of_list` qui prend une liste d'`int` et renvoie
> 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 *)
(3 * 2) + (5 / 2);;
3.2 +. (7.4 /. 2.0);;
"AC" ^ "/" ^ "DC";;
true || (false && false);;
(* && est prioritaire sur || *)
(false && false) || true;;
(* 3 < 2.0 provoque une erreur car 3 et 2.0 n'ont pas le même type *)
(**********************************************************************)
(* 1.1.2 *)
(* float_of_int 3.0 provoque une erreur car 3.0 n'est pas un int *)
float_of_int 3;;
int_of_float;;
int_of_float 3.5 + 2;;
float_of_int (int_of_float 3.6 + 2) +. 1.5;;
(not false) && false;;
2 < 3 && "b" < "ab";;
(**********************************************************************)
(* 1.1.3 *)
let x1 = 3.5 +. float_of_int 2 in
x1 +. 3.0
;;
let x1 = 3.5 +. float_of_int 2 in
let x2 = x1 *. x1 in
x2 *. 2.0
;;
(**********************************************************************)
(* 1.1.4 *)
(if true then 3 else 5) > 4
(**********************************************************************)
(* 1.2 *)
let f x = x + 1;;
3 + f 3
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 *)
let discriminant (a : float) (b : float) (c : float) : float =
(b *. b) -. (4.0 *. a *. c)
;;
discriminant;;
discriminant 2.0 8.0 8.0
(* Erreur de type sur l'utilisation de a dans la def suivante: *)
(*
let discriminant (a:int) (b:float) (c:float): float =
b *. b -. 4.0 *. a *. c;;
*)
(**********************************************************************)
(* 1.3.1 *)
type couleur = Rouge | Jaune | Bleu;;
(* La valeur Rouge est bien définie et reconnue comme étant de type couleur *)
Rouge;;
(* L'égalité est bien (pré)définie sur les couleurs *)
Rouge = Rouge;;
Rouge != Bleu;;
(* De même il existe un ordre par défaut sur les couleurs *)
Bleu > Jaune
type couleur = Rouge | Jaune | Bleu | Violet | Orange | Vert
type couleur =
| Rouge
| Jaune
| Bleu
| Violet
| Orange
| Vert
| RJB of int * int * int
;;
(* le type est "int * float" c'est-à-dire une paire d'int et de float *)
3, 5.6;;
(* c'est le triplet de type int * int * int *)
3, 5, 6
(**********************************************************************)
(* 1.3.2 *)
let nom_couleur c =
match c with
| Rouge -> "rouge"
| Jaune -> "jaune"
| Bleu -> "bleu"
| Vert -> "vert"
| Violet -> "violet"
| Orange -> "orange"
| _ -> "mélange"
(*
let nom_couleur c =
match c with
| Rouge -> "rouge"
| Jaune -> "jaune"
| Bleu -> "bleu"
| Vert -> "vert"
| Violet -> "violet"
| Orange -> "orange";;
Erreur: this pattern-matching is not exhaustive.
Elle indique que le match ne couvre pas tous les cas possibles.
*)
(*
let nom_couleur c =
match c with
| _ -> "mélange"
| Rouge -> "rouge"
| Jaune -> "jaune"
| Bleu -> "bleu"
| Vert -> "vert"
| Violet -> "violet"
| Orange -> "orange";;
Erreur: this pattern-matching is not exhaustive.
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 *)
let paragraphe_bottles (n : int) : string =
match n with
| 0 ->
"No more bottles of beer on the wall, no more bottles of beer.\n"
^ "Go to the store and buy some more, 99 bottles of beer on the wall."
| 1 ->
"1 bottle of beer on the wall, 1 bottle of beer.\n"
^ "Take one down and pass it around, no more bottles of beer on the wall."
| 2 ->
"2 bottles of beer on the wall, 2 bottles of beer.\n"
^ "Take one down and pass it around, 1 bottle of beer on the wall."
| k ->
string_of_int k ^ " bottles of beer on the wall, " ^ string_of_int k
^ " bottles of beer.\n" ^ "Take one down and pass it around, "
^ string_of_int (k - 1)
^ " bottles of beer on the wall."
;;
(* tests *)
paragraphe_bottles 0;;
paragraphe_bottles 1;;
paragraphe_bottles 2;;
paragraphe_bottles 7
(**********************************************************************)
(* 2.1 *)
(** 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 *)
let rec factorielle n = if n <= 1 then 1 else n * factorielle (n - 1)
(**********************************************************************)
(* 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 *)
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
@param l la liste dont on veut additionner les éléments
@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] *)
let rec join_s (l : string list) (sep : string) : string =
match l with
| [] -> ""
| s :: [] -> s
| s :: l2 ->
(* Plus général que le cas précédent car l2 n'est pas forcément vide.
Comme le match applique le premier cas dans l'ordre du programme,
Les listes à 1 élément ne passent pas dans le 3eme cas du match. *)
s ^ sep ^ join_s l2 sep
;;
(* 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 *)
let rec liste_n_0 (n : int) : int list =
if n <= 0 then [ 0 ] else n :: liste_n_0 (n - 1)
;;
(* 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 *)
let rec bottles_of_list (l : int list) : string list =
match l with
| [] -> []
| n :: l2 -> paragraphe_bottles n :: bottles_of_list l2
let chanson_99_bottles =
let nums = liste_n_0 99 in
let paragraphes = bottles_of_list nums in
join_s paragraphes "\n\n"
;;
print_endline chanson_99_bottles
# LIFPF: TP2 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;
- d'écrire le commentaire de description de la fonction **avant** d'écrire la fonction.
_Remarque:_ _ajouter_ un commentaire au-dessus de la fonction n'est pas la même chose que d'écrire le commentaire _avant_ d'écrire la fonction. N'oubliez pas: vous écrivez avant tout ce commentaire pour vous aider à écrire la fonction et ensuite seulement pour la documenter.
On prendra également soin d'écrire des tests pour chaque fonction en écrivant des expressions du type `ma_fonction valeur1 valeur2 = valeur attendue;;` et en vérifiant avec l'interpréteur que ces tests s'évaluent à `true`.
## 1. Concaténations de listes
Dans cette partie on souhaite implémenter des fonctions qui vont concaténer des listes.
### 1.1 Concaténation de deux listes
Implémenter la fonction `concatene` qui prend deux `int list` et renvoie la liste constituée des éléments de la première suivis des éléments de la seconde.
Quelques exemples de tests (à compléter):
```ocaml
concatene [ 1; 2; 3 ] [ 4; 5; 6 ] = [ 1; 2; 3; 4; 5; 6 ];;
concatene [] [ 4; 5; 6 ] = [ 4; 5; 6 ];;
```
### 1.2 Concaténation (applatissement) d'une liste de liste
Implémenter la fonction `applatit` qui prend une `(int list) list` et renvoie la liste constituée de la concaténation des éléments de cette liste de liste.
Cette fonction utilisera la fonction `concatene` codée auparavant.
Quelques exemples de tests (à compléter):
```ocaml
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` (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.
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
```ocaml
renverse_ajoute [ 1; 2; 3 ] [ 4; 5; 6 ] = [ 3; 2; 1; 4; 5; 6]
```
On remarque que les égalités suivantes sont vraies :
```ocaml
renverse_ajoute [ 1; 2; 3 ] [ 4; 5; 6 ]
= renverse_ajoute [ 2; 3 ] [ 1; 4; 5; 6 ]
= renverse_ajoute [ 3 ] [ 2; 1; 4; 5; 6 ]
= renverse_ajoute [] [ 3; 2; 1; 4; 5; 6 ]
```
On peut aussi remarquer que `renverse` peut se coder très facilement à partir de `renverse_ajoute` puisque `renverse l = renverse_ajoute l []`.
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 ...`.
> 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.
### 3.1 Insertion dans une liste triée
On commence donc par coder une fonction `insertion` qui insère un élément _à la bonne position_ dans une liste triée par ordre croissant.
Quelques tests à compléter:
```ocaml
insertion 3 [ 1; 2; 4; 5 ] = [ 1; 2; 3; 4; 5 ];;
insertion 3 [ 1; 2; 3; 4; 5 ] = [ 1; 2; 3; 3; 4; 5 ];;
```
### 3.2 Tri
Coder la fonction `tri_insertion` en utilisant `insertion`.
Quelques tests à compléter:
```ocaml
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.
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.
### 4.2 Fonction de recherche
Coder la fonction `cherche` qui va chercher une clé dans une liste d'association et renvoyer la valeur associée lorsqu'elle existe et utilisant le type `resultat`.
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 leurs arguments**.
On donne quelques exemples dans le tableau suivant:
| Notation habituelle | Notation polonaise |
| -------------------- | ------------------ |
| 3 \* 2 | \* 3 2 |
| 7 / 3 | / 7 3 |
| (7 / 3) + 5 | + / 7 3 5 |
| (3\*2) - ((7/3) + 5) | - \* 3 2 + / 7 3 5 |
On peut remarquer que cette notation permet de se passer de parenthèses.
Avant d'évaluer ce type d'expressions il faut pouvoir les représenter.
On va donc se munir d'un type pour représenter les opérateurs, d'un type représentant les éléments d'expression (c'est-à-dire un opérateur ou un nombre):
```ocaml
type binop = Plus | Moins | Mult | Div
type elt_expr = Op of binop | Cst of int
```
De plus l'évaluation d'une expression peut mal se passer pour deux raisons: une division par zéro ou une expression mal construite.
Par exemple `+ - 2 3` est mal construite car il manque un argument au `+`.
On va donc créer un type pour représenter un résultat ou une erreur:
```ocaml
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ê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:
```ocaml
eval_op Plus (Ok 1) (Ok 2) = Ok 3;;
eval_op Moins (Ok 2) (Ok 3) = Ok (-1);;
eval_op Div (Ok 3) (Ok 0) = ErrDivZero;;
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`.
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'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.
Quelques tests à compléter:
```ocaml
eval_expr [ Cst 3 ] = [ Ok 3 ];;
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.
(**********************************************************************)
(**********************************************************************)
(* 1.1 *)
(**
Concatène 2 listes
@param l1 la première liste à concaténer
@param l2 la deuxième liste
@return la liste résultant de la concaténation de l1 avec l2
*)
let rec concatene (l1 : int list) (l2 : int list) : int list =
match l1 with [] -> l2 | x :: l1' -> x :: concatene l1' l2
;;
(* Quelques tests *)
concatene [ 1; 2; 3 ] [ 4; 5; 6 ] = [ 1; 2; 3; 4; 5; 6 ];;
concatene [] [ 4; 5; 6 ] = [ 4; 5; 6 ];;
concatene [ 1; 2; 3 ] [] = [ 1; 2; 3 ];;
concatene [] [] = []
(**********************************************************************)
(* 1.2 *)
(**
applatit prend une liste de liste et renvoie la liste resultant de la concatenation des sous-listes
@param ll la liste contenant les liste à concatener
@return la liste contenant les élements des sous-listes
*)
let rec applatit (ll : int list list) : int list =
match ll with [] -> [] | l :: ll' -> concatene l (applatit ll')
;;
(* Quelques tests *)
applatit [ [ 1; 2 ]; [ 3; 4; 5 ]; []; [ 6 ] ] = [ 1; 2; 3; 4; 5; 6 ];;
applatit [ [ 1 ] ] = [ 1 ];;
applatit [ [] ] = [];;
applatit [] = []
(**
applatit2 prend une liste de liste et renvoie la liste resultant de la concatenation des sous-listes.
applatit2 n'utilise pas concatene
@param ll la liste contenant les liste à concatener
@return la liste contenant les élements des sous-listes
*)
let rec applatit2 (ll : int list list) : int list =
match ll with
| [] -> []
| [] :: ll' -> applatit2 ll'
| (n :: l') :: ll' -> n :: applatit2 (l' :: ll')
;;
(* Quelques tests *)
applatit2 [ [ 1; 2 ]; [ 3; 4; 5 ]; []; [ 6 ] ] = [ 1; 2; 3; 4; 5; 6 ];;
applatit2 [ [ 1 ] ] = [ 1 ];;
applatit2 [ [] ] = [];;
applatit2 [] = []
(**********************************************************************)
(**********************************************************************)
(* 2 *)
(**
Renverse une liste.
@param l la liste à renverse.
@return la liste renversée
*)
let renverse =
(*
Cette fonction concatène son premier argument renversé à son second
@param lr la liste à renverser
@param lc la liste à laquelle on veut concaténer des éléments
@return la concaténation de lr renversée et de lc
*)
let rec renverse_ajoute (lr : int list) (lc : int list) : int list =
match lr with
| [] -> lc
| n :: lr' ->
(* on ajoute n en tête de la liste lc et on fait l'appel récursif
qui ajoutera les autres éléments devant *)
renverse_ajoute lr' (n :: lc)
in
fun (l : int list) -> renverse_ajoute l []
(**********************************************************************)
(**********************************************************************)
(* 3.1 *)
(* Insersion dans une liste triée *)
(**
Insère un entier dans une liste triée.
@param n la valeur à insérer
@param l la liste dans laquelle on fait l'insertion
@return une nouvelle liste contenant les élément de l et n et elle-même
*)
let rec insertion (n : int) (l : int list) : int list =
match l with
| [] -> [ n ] (* on aurait pu écrire [n] *)
| k :: l' ->
if k >= n (* on veut placer n en tête de liste *) then n :: l
(* attention c'est n :: l et pas n :: l'
on aurait aussi pu écrire n :: k :: l' *)
else k :: insertion n l'
;;
(* Quelques tests*)
insertion 3 [ 1; 2; 4; 5 ] = [ 1; 2; 3; 4; 5 ];;
insertion 3 [ 1; 2; 3; 4; 5 ] = [ 1; 2; 3; 3; 4; 5 ];;
insertion 3 [ 4; 5 ] = [ 3; 4; 5 ];;
insertion 3 [ 1; 2 ] = [ 1; 2; 3 ];;
insertion 3 [] = [ 3 ]
(**********************************************************************)
(* 3.2 *)
(**
Trie une liste en utilisant l'algorithme de tri par insertion
@param l la liste à trier
@return la liste contenant les éléments de l triés
*)
let rec tri_insertion (l : int list) : int list =
match l with
| [] -> []
| n :: l' ->
(* Ici, on insère dans le reste de la liste triée *)
insertion n (tri_insertion l')
;;
(* Quelques tests *)
tri_insertion [ 1; 4; 2; 3 ] = [ 1; 2; 3; 4 ];;
tri_insertion [ 1; 2; 3; 4 ] = [ 1; 2; 3; 4 ];;
tri_insertion [ 4; 3; 2; 1 ] = [ 1; 2; 3; 4 ];;
tri_insertion [ 1 ] = [ 1 ];;
tri_insertion [] = []
(**********************************************************************)
(**********************************************************************)
(* 4.1 *)
(**
Résultat d'une recherche
*)
type resultat =
(* une valeur a été trouvée,
on associe la donnée de cette valeur au constructeur,
elle a donc le type string*)
| Trouve of string
(* On a rien trouvé, pas de donnée associée *)
| Rien
(* 4.2 *)
(**
Cherche la valeur associée à une clé dans une liste de paires (clé,valeur).
@param cle la clé
@param la la liste d'association
@return Trouve v si la paire (cle,v) est dans la liste, Rien sinon
*)
let rec cherche (cle : int) (la : (int * string) list) : resultat =
match la with
| [] -> Rien
| (cle', v) :: la' ->
if cle' = cle (* si on a trouvé la clé *) then Trouve v
else (* on cherche dans le reste de la liste *)
cherche cle la'
;;
(* Quelques tests *)
cherche 3 [ (1, "a"); (3, "b"); (5, "c") ] = Trouve "b";;
cherche 3 [ (3, "b"); (5, "c") ] = Trouve "b";;
cherche 3 [ (1, "a"); (3, "b") ] = Trouve "b";;
cherche 3 [ (5, "b"); (1, "a") ] = Rien;;
cherche 3 [] = Rien
(**********************************************************************)
(**********************************************************************)
(**
Type représentant les opérateurs binaires.
*)
type binop = Plus | Moins | Mult | Div
(**
Type représentant les morceaux d'expression.
*)
type elt_expr = Op of binop | Cst of int
(**
Type représentant les résultats.
*)
type resultat =
| Ok of int
| ErrDivZero
| ErrExpr
(**
Évalue le résultat d'une opération binaire.
Prend l'opération en argument ainsi que deux résultats.
S'il l'un des arguments est une erreur, cette (une de ces)
erreur est renvoyée comme résultat.
@param op l'opération à effectuer
@param a1 la première valeur à passer à op
@param a2 la deuxième valeur à passer à op
@return le réultat de l'opération ou une erreur le cas échéant
*)
(* 5.1 *)
let eval_op (op : binop) (a1 : resultat) (a2 : resultat) : resultat =
match (a1, a2) with
| Ok v1, Ok v2 -> (
match op with
| Plus -> Ok (v1 + v2)
| Moins -> Ok (v1 - v2)
| Mult -> Ok (v1 * v2)
| Div -> if v2 = 0 then ErrDivZero else Ok (v1 / v2))
| Ok _, err -> err
| err, _ -> err
;;
(* Quelques tests *)
eval_op Plus (Ok 1) (Ok 2) = Ok 3;;
eval_op Moins (Ok 2) (Ok 3) = Ok (-1);;
eval_op Div (Ok 3) (Ok 0) = ErrDivZero;;
eval_op Div (Ok 5) (Ok 2) = Ok 2;;
eval_op Mult (Ok 7) (Ok 6) = Ok 42;;
eval_op Plus ErrDivZero (Ok 5) = ErrDivZero;;
eval_op Mult ErrExpr (Ok 4) = ErrExpr;;
eval_op Div (Ok 5) ErrExpr = ErrExpr;;
eval_op Moins (Ok 4) ErrDivZero = ErrDivZero;;
eval_op Plus ErrDivZero ErrExpr = ErrDivZero
(**********************************************************************)
(* 5.2 *)
(**
Évalue une suite d'expressions et donne la liste des résultats
@param la liste d'éléments d'expression formant la liste suite d'expressions
@return le résultat de l'évaluation des expressions
*)
let rec eval_expr (le : elt_expr list) : resultat list =
match le with
| [] -> []
| Cst n :: le' -> Ok n :: eval_expr le'
| Op op :: le' -> (
match eval_expr le' with
| r1 :: r2 :: rl -> eval_op op r1 r2 :: rl
| _ -> [ ErrExpr ])
;;
(* Quelques tests *)
eval_expr [ Cst 3 ] = [ Ok 3 ];;
eval_expr [ Op Mult; Cst 3; Cst 2 ] = [ Ok 6 ];;
eval_expr [ Op Div; Cst 7; Cst 3 ] = [ Ok 2 ];;
eval_expr [ Op Moins; Cst 3; Cst 1 ] = [ Ok 2 ];;
eval_expr [ Op Plus; Op Div; Cst 7; Cst 3 ] = [ ErrExpr ];;
eval_expr [ Op Plus; Op Div; Cst 7; Cst 3; Cst 5 ] = [ Ok 7 ];;
eval_expr [ Op Plus; Op Div; Cst 7; Op Moins; Cst 2; Cst 2; Cst 3 ]
= [ ErrDivZero ]
;;
eval_expr [ Op Plus; Cst 3; Cst 5; Op Moins; Cst 2; Cst 7 ] = [ Ok 8; Ok (-5) ]
# LIFPF TP3: récursion sur les arbres
**Lire complètement** chaque partie avant de la coder.
## 1. Arbres binaires
Dans cette partie on va travailler sur les arbres binaires.
### 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.
>
> Définir une fonction récursive `taille_ab` qui donne la taille d'un arbre binaire d'`int`, c'est-à-dire le nombre d'`int` qui y sont stockés. Tester la fonction en utilisant `assert` comme montré ci-dessous (en supposant que les variables globales `ab1` et `ab2` aient été bien définies).
```ocaml
assert (taille_ab ABVide = 0);;
assert (taille_ab ab1 = 1);;
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`.
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:
- Un arbre binaire **de recherche** est un arbre binaire tel que pour tout noeud, les éléments du fils **gauche** sont tous _plus petits que l'élément du noeud_ et les éléments du fils **droit** sont _plus grand que l'élément du noeud_.
- 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**.
#### 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.
On rappelle que dans le cadre de l'UE, on ne fait **pas** de modification en place des données. Une telle insertion se fait donc forcément en _reconstruisant un nouvel arbre_ (même si certaines parties de l'ancien arbre peuvent être réutilisées).
> Tester `insere_arbre_bin_recherche` en utilisant `assert`.
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.
On pourra utiliser la concaténation de liste du TP2 ou son équivalent dans la [bibliothèque standard OCaml](<https://v2.ocaml.org/api/Stdlib.html#VAL(@)>) pour simplifier le codage de cette fonction.
Il faudra bien réfléchir à la position de l'élément du noeud dans le résultat.
> Tester la fonction `list_of_arbre_bin` avec `assert`.
#### 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**.
On utilisera `insere_arbre_bin_recherche` dans cette fonction.
> Tester `arbre_bin_rech_of_int_list` en utilisant `assert` et `list_of_arbre_bin` afin de simplifier l'écriture des tests.
Quelle propriété des arbres est-elle utile pour prédire le résultat de l'appel `list_of_arbre_bin (arbre_bin_rech_of_int_list l)` sur une liste `l` donnée ?
> 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`.
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.
On commence par se donner un type pour représenter les différents opérateurs possibles:
```ocaml
type binop = Plus | Moins | Mult
```
On se donne ensuite un type pour représenter les expressions :
```ocaml
type expr =
| Cst of int
| Binop of binop * expr * expr
```
> Créer quelques variables globales contenant des expressions pour les tests à venir.
### 2.1 Affichage et évaluation
#### 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édemment créées.
#### 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édemment créées.
Prévenez votre chargé de TP afin de valider la partie 2.1.
### 2.2 Division et erreurs
#### 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).
#### 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`.
On va donc créer un type `eval_err` pour représenter les erreurs possibles (pour le moment une seule erreur possible: la division par zéro):
```ocaml
type eval_err = DivZero
```
On créée également un type `resultat` qui est soit une erreur soit un int:
```ocaml
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 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.
Prévenez votre chargé de TP pour valider la partie 2.2.
### 2.3 Variables
#### 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:
```ocaml
type expr =
| Cst of int
| Binop of binop * expr * expr
| Var of string (* nouveau constructeur dans 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 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.
> Ajouter un constructeur `VarNonDef` au type `eval_err`.
>
> Modifier la fonction `eval_expr` pour qu'elle prenne un argument supplémentaire : une liste de paires `string * int` qui représentera **la valeur associée à chaque variable**. Utiliser cet argument pour gérer le cas des variables dans les expressions.
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.
### 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`.
```ocaml
type expr =
| Cst of int
| Binop of binop * expr * expr
| Var of string
| Let of string * expr * 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 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 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`.
Prévenez votre chargé de TP pour valider la partie 2.3.
(* LIFPF TP3 Récursion sur les arbres *)
(**********************************************************************)
(* Arbres binaires *)
(**********************************************************************)
(**
Arbres binaires avec feuilles vides,
le contenu est seulement sur les noeuds.
*)
type arbre_bin = ABVide | ABNoeud of int * arbre_bin * arbre_bin
(* Quelques arbres pour tester *)
let ab1 = ABNoeud (3, ABVide, ABVide)
let ab2 = ABNoeud (5, ab1, ABVide)
let ab3 = ABNoeud (7, ABVide, ab1)
let ab4 = ABNoeud (11, ab2, ab3)
(**
Taille d'un arbre binaire.
@param a l'arbre dont on veut calculer la taille
@return le nombre d'int stockés dans l'arbre
*)
let rec taille_ab (a : arbre_bin) : int =
match a with
| ABVide -> 0
| ABNoeud (_, fg, fd) -> 1 + taille_ab fg + taille_ab fd
;;
assert (taille_ab ab1 = 1);;
assert (taille_ab ab2 = 2);;
assert (taille_ab ab3 = 2);;
assert (taille_ab ab4 = 5)
(**
Fait le produit des éléments d'un arbre binaire.
Un arbre vide aura 1 comme produit
@param a l'arbre dont on veut faire le produit des éléments
@return le produit (1 pour l'arbre vide)
*)
let rec produit_ab (a : arbre_bin) : int =
match a with
| ABVide -> 1
| ABNoeud (n, fg, fd) -> n * produit_ab fg * produit_ab fd
;;
assert (produit_ab ABVide = 1);;
assert (produit_ab ab1 = 3);;
assert (produit_ab ab2 = 15);;
assert (produit_ab ab3 = 21);;
assert (produit_ab ab4 = 3465)
(**
Construit la liste des éléments d'un arbre binaire. Les éléments sont produits
dans l'ordre de parcours infix, c'est à dire les éléments du fils gauche puis
l'élément du noeud puis ceux fils droit.
@param a l'arbre binaire dont on veut les éléments
@return la liste des éléments de l'arbre
*)
let rec list_of_arbre_bin (a : arbre_bin) : int list =
match a with
| ABVide -> []
| ABNoeud (n, fg, fd) ->
(* On peut aussi utiliser la fonction concatene du TP2 *)
list_of_arbre_bin fg @ (n :: list_of_arbre_bin fd)
;;
assert (list_of_arbre_bin ABVide = []);;
assert (list_of_arbre_bin ab1 = [ 3 ]);;
assert (list_of_arbre_bin ab2 = [ 3; 5 ]);;
assert (list_of_arbre_bin ab3 = [ 7; 3 ]);;
assert (list_of_arbre_bin ab4 = [ 3; 5; 11; 7; 3 ])
(**
Insère un élément dans un arbre binaire de recherche.
@param e l'élément à insérer
@param a l'arbre dans lequel on fait l'insersion
@return un arbre binaire de recherche contenant les éléments de a ainsi que e
*)
let rec insere_arbre_bin_recherche (e : int) (a : arbre_bin) : arbre_bin =
match a with
| ABVide -> ABNoeud (e, ABVide, ABVide)
| ABNoeud (x, fg, fd) ->
if e < x then ABNoeud (x, insere_arbre_bin_recherche e fg, fd)
else ABNoeud (x, fg, insere_arbre_bin_recherche e fd)
let abr1 = insere_arbre_bin_recherche 7 ABVide
let abr2 = insere_arbre_bin_recherche 5 abr1
let abr3 = insere_arbre_bin_recherche 3 abr2
let abr4 = insere_arbre_bin_recherche 11 abr3;;
assert (list_of_arbre_bin abr1 = [ 7 ]);;
assert (list_of_arbre_bin abr2 = [ 5; 7 ]);;
assert (list_of_arbre_bin abr3 = [ 3; 5; 7 ]);;
assert (list_of_arbre_bin abr4 = [ 3; 5; 7; 11 ])
(**
Créée un arbre binaire de recherche contenant les éléments de la liste
@param l la liste contenant les éléments à placer dans l'arbre à créer
@return l'arbre binaire de recherche contenant les éléments de l
*)
let rec arbre_bin_rech_of_int_list (l : int list) : arbre_bin =
match l with
| [] -> ABVide
| x :: l' -> insere_arbre_bin_recherche x (arbre_bin_rech_of_int_list l')
;;
assert (list_of_arbre_bin (arbre_bin_rech_of_int_list []) = []);;
assert (list_of_arbre_bin (arbre_bin_rech_of_int_list [ 3 ]) = [ 3 ]);;
assert (list_of_arbre_bin (arbre_bin_rech_of_int_list [ 3; 5 ]) = [ 3; 5 ]);;
assert (list_of_arbre_bin (arbre_bin_rech_of_int_list [ 5; 3 ]) = [ 3; 5 ]);;
assert (
list_of_arbre_bin (arbre_bin_rech_of_int_list [ 1; 2; 3; 4 ]) = [ 1; 2; 3; 4 ])
;;
assert (
list_of_arbre_bin (arbre_bin_rech_of_int_list [ 4; 2; 1; 3 ]) = [ 1; 2; 3; 4 ])
(**
Trie une list d'int en utilisant un arbre binaire de recherche
@param l la liste à trier
@return la liste triée
*)
let tri_abr (l : int list) : int list =
list_of_arbre_bin (arbre_bin_rech_of_int_list l)
;;
assert (tri_abr [] = []);;
assert (tri_abr [ 3 ] = [ 3 ]);;
assert (tri_abr [ 3; 5 ] = [ 3; 5 ]);;
assert (tri_abr [ 5; 3 ] = [ 3; 5 ]);;
assert (tri_abr [ 1; 2; 3; 4 ] = [ 1; 2; 3; 4 ]);;
assert (tri_abr [ 4; 2; 1; 3 ] = [ 1; 2; 3; 4 ])
(**********************************************************************)
(* Expressions arithmétiques et variables *)
(**********************************************************************)
(**
Type représentant les opérateurs binaires.
*)
type binop = Plus | Moins | Mult | Div
(**
Expressions arithmétiques + let
*)
type expr =
| Cst of int
| Binop of binop * expr * expr
| Var of string
| Let of string * expr * expr
(** affichage **)
let rec string_of_expr (e : expr) : string =
let string_of_binop (b : binop) =
match b with Plus -> " + " | Moins -> " - " | Mult -> " * " | Div -> " / "
in
match e with
| Cst n -> string_of_int n
| Binop (op, l, r) ->
"(" ^ string_of_expr l ^ string_of_binop op ^ string_of_expr r ^ ")"
| Var x -> x
| Let (v, e1, e2) ->
"(let " ^ v ^ " = " ^ string_of_expr e1 ^ " in " ^ string_of_expr e2 ^ ")"
(** Erreurs *)
type eval_err = DivZero | VarNonDef
(** Résultats: int ou erreur *)
type resultat = Ok of int | Err of eval_err
(**
Évalue une expression dans un environnement
*)
let rec eval_expr (e : expr) (env : (string * int) list) : resultat =
match e with
| Cst n -> Ok n
| Binop (op, e1, e2) -> (
match (eval_expr e1 env, eval_expr e2 env) with
| Ok v1, Ok v2 -> (
match op with
| Plus -> Ok (v1 + v2)
| Moins -> Ok (v1 - v2)
| Mult -> Ok (v1 * v2)
| Div -> if v2 = 0 then Err DivZero else Ok (v1 / v2))
| Err e, _ -> Err e
| _, Err e -> Err e)
| Var x -> (
match List.assoc_opt x env with None -> Err VarNonDef | Some n -> Ok n)
| Let (x, e1, e2) -> (
match eval_expr e1 env with
| Ok v1 -> eval_expr e2 ((x, v1) :: env)
| Err e -> Err e)
let e1 = Cst 3
let e2 = Binop (Plus, Cst 3, Cst 5)
let e3 = Binop (Div, Cst 3, Cst 0)
let e4 = Let ("a", Cst 3, Binop (Moins, Var "a", Cst 3))
let e5 = Let ("a", Cst 3, Var "b");;
assert (eval_expr e1 [] = Ok 3);;
assert (eval_expr e2 [] = Ok 8);;
assert (eval_expr e3 [] = Err DivZero);;
assert (eval_expr e4 [] = Ok 0);;
assert (eval_expr e5 [] = Err VarNonDef);;
assert (eval_expr e5 [ ("b", 11) ] = Ok 11)
(**********************************************************************)
# 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 interactif.
## 1. Arbres n-aires
Un arbre n-aire est un arbre dans lequel chaque noeud peut avoir un _nombre quelconque de fils_.
Dans le cadre de cet exercice, on va considérer des arbres dont les données sont stockées **uniquement** dans les feuilles.
Une forêt est une liste d'arbres.
Un noeud contiendra donc simplement une forêt.
### 1.1. Type arbre n-aire
> Définir un type `'a arbre_n` pour représenter les arbres n-aires contenant des éléments de type `'a`. Un arbre est soit _une feuille contenant un élément_, soit un _noeud contenant une forêt_.
>
> Définir quelques arbres pour pouvoir tester.
>
> Donner deux exemples d'arbres ne contenant **aucun** élément.
>
> Quel est le type inféré par OCaml pour ces arbres ? Pourquoi ?
### 1.2. Hauteur d'un arbre
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`.
**Appeler votre chargé de TP pour faire valider votre progression**
### 1.3. Éléments d'un arbre
On souhaite maintenant pouvoir construire la liste des éléments d'un arbre.
Comme un arbre peut contenir une forêt, il faut **deux fonctions mutuellement récursives** :
- une fonction qui donne les éléments _d'un arbre_;
- une autre qui donne les éléments _d'une forêt_.
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.
>
> 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.
>
> Modifier les tests précédents pour tester `list_of_arbre` et pas `list_of_arbre_aux` (qui est maintenant masquée).
### 1.4. Minimum d'un arbre
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`.
> 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
On peut remarquer que dans le code de `minimum` et `minimum_foret`, la fonction `min` est utilisée pour combiner deux résultats.
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`.
> 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`.
**Faire valider votre progression par votre chargé de TP**
## 2. FIFOs basées sur des listes
On souhaite implémenter une structure de file (en anglais **FIFO**: _First In First Out_).
Dans ce type de structure, on veut pouvoir ajouter et retirer des éléments de manière à ce que les éléments soient retirés _dans l'ordre où ils ont été ajoutés_.
Une implémentation naïve de FIFO basée sur une liste pose des problèmes de performance :
- 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 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é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.
On profite de ce transfert pour renverser l'ordre des éléments (comme dans `renverse_ajoute` du TP2).
Chaque élément est donc placé exactement _une fois dans chacune des listes_.
Donc remplir et vider la file s'effectue donc avec un nombre d'opérations linéaire dans le nombre d'éléments ajoutés et retirés, contrairement aux versions naïves pour lesquelles le nombre d'opérations est quadratique.
### 2.1. Exemple
###### 1. On suppose qu'on a la situation de départ suivante :
Gauche
```mermaid
flowchart LR
4 --> 3 --> vide
```
Droite
```mermaid
flowchart LR
1 --> 2 --> vide
```
###### 2. On retire le premier élément (1) :
Gauche
```mermaid
flowchart LR
4 --> 3 --> vide
```
Droite
```mermaid
flowchart LR
2 --> vide
```
###### 3. On ajoute un élément (42) :
Gauche
```mermaid
flowchart LR
42 --> 4 --> 3 --> vide
```
Droite
```mermaid
flowchart LR
2 --> vide
```
###### 4. On retire le premier élément (2) :
Gauche
```mermaid
flowchart LR
42 --> 4 --> 3 --> vide
```
Droite
```mermaid
flowchart LR
vide
```
###### 5. On retire le premier élément (3) :
On effectue d'abord le transfert des éléments de gauche à droite, puis on retire l'élément comme avant
**Transfert :**
Gauche
```mermaid
flowchart LR
vide
```
Droite
```mermaid
flowchart LR
3 --> 4 --> 42 --> vide
```
**Retirer 3 :**
Gauche
```mermaid
flowchart LR
vide
```
Droite
```mermaid
flowchart LR
4 --> 42 --> vide
```
### 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.
<!-- 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.
<!-- 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.
**Faire valider votre progression par votre chargé de TP**
### 2.3 Récupération des éléments et transfert
On va tout d'abord coder la fonction `transfert_fifo` qui déplace les éléments de la liste de gauche dans celle de droite en inversant leur ordre. On pourra s'inspirer de `renverse_ajoute` du TP2 pour cette fonction.
> Coder la fonction `transfert_fifo` et la tester avec `assert` et les fifos exemples créées précédement.
On peut maintenant coder la fonction `pop_fifo` qui va récupérer un élément dans la fifo.
Dans cette fonction, on distinguera le cas d'une fifo avec au moins un élément à droite de celle avec la liste droite vide. Dans le deuxième cas, on utilisera `transfert_fifo` pour basculer les éléments de gauche à droite avant de prendre la tête de liste à droite.
Le résultat de `pop_fifo` sera une paire contenant d'une part la fifo sans l'élément retiré et d'autre part l'élément retiré. Comme une fifo peut être vide, il n'y a pas forcément d'élément à retirer. La valeur de ce dernier sera donc une option, et considérera par convention que retirer un élément d'une fifo vide produit une fifo vide (et `None` pour la valeur de l'élément retiré). Le type de `pop_fifo` sera donc `'a fifo -> ('a fifo * 'a option)`.
> 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**
(**********************************************************************)
(* Arbres n-aires *)
(**********************************************************************)
(** Arbre avec un nombre quelconque de fils *)
type 'a arbre_n = Feuille of 'a | Noeud of 'a arbre_n list
let a1 = Feuille 1
let a2 = Feuille 2
let a3 = Noeud []
let a4 = Noeud [ a1 ]
let a5 = Noeud [ a1; a2 ]
let a6 = Noeud [ a1; a2; a3; a4; a5 ]
let a_vide_1 = Noeud []
let a_vide_2 = Noeud [ Noeud [] ]
(* Le type de ces arbres vide est 'a arbre_n. En effet, comme ces arbres ne
contiennent pas d'éléments ils peuvent être vus comme des arbresavec ce qu'on
veut comme type d'élément. *)
let rec hauteur (a : 'a arbre_n) : int =
match a with Feuille _ -> 1 | Noeud l -> hauteur_foret l + 1
and hauteur_foret (l : 'arbre_n list) : int =
match l with
| [] -> 0
| a :: l' -> max (hauteur a) (hauteur_foret l')
;;
assert (hauteur a1 = 1);;
assert (hauteur a3 = 1);;
assert (hauteur a4 = 2);;
assert (hauteur a5 = 2);;
assert (hauteur a6 = 3)
(**
Renvoie une liste contenant tous les éléments de l'arbre
@param a: l'arbre
@return la liste de ses éléments
*)
let list_of_arbre (a : 'a arbre_n) : 'a list =
let rec list_of_arbre_aux (a : 'a arbre_n) (acc : 'a list) : 'a list
=
match a with
| Feuille x -> x :: acc
| Noeud f -> list_of_foret f acc
and list_of_foret (f : 'a arbre_n list) (acc : 'a list) : 'a list =
match f with
| [] -> acc
| a :: f' -> list_of_arbre_aux a (list_of_foret f' acc)
in
list_of_arbre_aux a []
;;
assert (list_of_arbre a1 = [ 1 ]);;
assert (list_of_arbre a4 = [ 1 ]);;
assert (list_of_arbre a5 = [ 1; 2 ]);;
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 petit élément de l'arbre
*)
let rec minimum (arbre : 'a arbre_n) : 'a option =
match arbre with
| Feuille x -> Some x
| Noeud la -> minimum_foret la
(**
[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 petit élément de la forêt
*)
and minimum_foret (la : 'a arbre_n list) : 'a option =
match la with
| [] -> None
| a :: la' -> (
match minimum_foret la' with
| None -> minimum a
| Some n -> (
match minimum a with
| None -> Some n
| Some n' -> Some (min n n')))
;;
assert (minimum a1 = Some 1);;
assert (minimum a3 = None);;
assert (minimum a4 = Some 1);;
assert (minimum a5 = Some 1);;
assert (minimum a6 = Some 1)
(**
[reduce f a] renvoie:
- None si a ne contient aucun élément
- Some x si a contient un seul élément x
- Some x où x est le résultat de la combinaison des éléments de a en utilisant f
@param f la fonction de combinaison des éléments
@param a l'arbre qui contient les éléments
*)
let rec reduce (f : 'a -> 'a -> 'a) (arbre : 'a arbre_n) : 'a option =
match arbre with Feuille x -> Some x | Noeud l -> reduce_foret f l
(**
[reduce_foret f l] renvoie:
- None si l (en tant que forêt) ne contient aucun élément
- Some x si l contient un seul élément x
- Some x où x est le résultat de la combinaison des éléments de l en utilisant f
@param f la fonction de combinaison des éléments
@param la forêt qui contient les éléments
*)
and reduce_foret (f : 'a -> 'a -> 'a) (la : 'a arbre_n list) :
'a option =
match la with
| [] -> None
| a :: la' -> (
match reduce_foret f la' with
| None -> reduce f a
| Some n -> (
match reduce f a with
| None -> Some n
| Some n' -> Some (f n n')))
;;
assert (reduce min a1 = Some 1);;
assert (reduce min a3 = None);;
assert (reduce min a4 = Some 1);;
assert (reduce min a5 = Some 1);;
assert (reduce min a6 = Some 1);;
assert (reduce ( + ) a1 = Some 1);;
assert (reduce ( + ) a3 = None);;
assert (reduce ( + ) a5 = Some 3);;
assert (reduce ( + ) a6 = Some 7)
(**********************************************************************)
(* Files (FIFO) implémentées avec deux listes *)
(**********************************************************************)
type 'a fifo = Fifo of ('a list * 'a list)
(** File vide *)
let empty_fifo : 'a fifo = Fifo ([], [])
(**
[push_fifo e f] Ajoute e dans f
@param e l'élément a ajouter
@param f la fifo dans laquelle on veut ajouter l'élément
@return la fifo contenant les éléments de f puis e
*)
let push_fifo (e : 'a) (f : 'a fifo) : 'a fifo =
match f with Fifo (l1, l2) -> Fifo (e :: l1, l2)
let f1 = push_fifo 1 empty_fifo
let f2 = push_fifo 2 f1
let f3 = push_fifo 3 f2
let f4 = push_fifo 4 f3;;
assert (f1 = Fifo ([ 1 ], []));;
assert (f2 = Fifo ([ 2; 1 ], []));;
assert (f3 = Fifo ([ 3; 2; 1 ], []));;
assert (f4 = Fifo ([ 4; 3; 2; 1 ], []))
(**
[push_list_fifo l f] ajoute les éléments de l à la file f
@param l les éléments à ajouter
@param f la file dans laquelle ajouter les éléments
@return la file contenant les éléments de f puis les éléments de l
*)
let rec push_list_fifo (l : 'a list) (f : 'a fifo) : 'a fifo =
match l with
| [] -> f
| x :: l' -> push_list_fifo l' (push_fifo x f)
;;
assert (push_list_fifo [] empty_fifo = empty_fifo);;
assert (push_list_fifo [] f2 = f2);;
assert (push_list_fifo [ 1 ] empty_fifo = f1);;
assert (push_list_fifo [ 3; 4 ] f2 = f4);;
assert (push_list_fifo [ 1; 2; 3; 4 ] empty_fifo = f4)
(**
Fonction utilitaire transférant tous les éléments de la liste de gauche dans
celle de droite en en renversant l'ordre au passage.
*)
let rec transfert_fifo (f : 'a fifo) : 'a fifo =
match f with
| Fifo ([], l2) -> Fifo ([], l2)
| Fifo (x :: l1, l2) -> transfert_fifo (Fifo (l1, x :: l2))
;;
assert (transfert_fifo f4 = Fifo ([], [ 1; 2; 3; 4 ]));;
assert (transfert_fifo f1 = Fifo ([], [ 1 ]))
(**
[pop_fifo f] renvoie le premier élément de f s'il y en a un, ainsi que la file contenant le reste des éléments de f.
@param f la file dans laquelle on veut prendre un élément
@return (f',r) où
- f' est la file contenant les éléments de f sauf le premier
- r est Some x si f a pour premier élément x ou bien None si f est vide
*)
let pop_fifo (f : 'a fifo) : 'a fifo * 'a option =
match f with
| Fifo (l1, []) -> (
match transfert_fifo f with
| Fifo (_, []) -> (Fifo ([], []), None)
| Fifo (_, x :: l2') -> (Fifo ([], l2'), Some x))
| Fifo (l1, x :: l2') -> (Fifo (l1, l2'), Some x)
;;
assert (pop_fifo empty_fifo = (empty_fifo, None));;
assert (pop_fifo f1 = (empty_fifo, Some 1));;
assert (pop_fifo f2 = (Fifo ([], [ 2 ]), Some 1));;
assert (pop_fifo (fst (pop_fifo f2)) = (empty_fifo, Some 2))
(**
Renvoie tous les éléments de la file dans l'ordre de celle-ci
@param f la file dont on veut les éléments
@return une liste contenant les éléments de f dans l'ordre
*)
let rec pop_all_fifo (f : 'a fifo) : 'a list =
match pop_fifo f with
| _, None -> []
| f', Some x -> x :: pop_all_fifo f'
;;
assert (pop_all_fifo empty_fifo = []);;
assert (pop_all_fifo f1 = [ 1 ]);;
assert (pop_all_fifo f4 = [ 1; 2; 3; 4 ]);;
(* Un test mélangeant les opérations de push et de pop de la file *)
assert (
pop_all_fifo (push_list_fifo [ 3; 4 ] (fst (pop_fifo f2)))
= [ 2; 3; 4 ])