Programmation Fonctionnelle, TD6

Figure
Version & licenses
Creative Commons License

Programmation Fonctionnelle, TD6 : Types algébriques II.

Guyslain Naves

Exercice 1 : Les arbres enracinés

Nous souhaitons travailler avec des arbres d'arités arbitraires, dont les feuilles sont des entiers. Les nœuds internes ne possèdent pas d'information, sinon la liste de leurs fils.

  1. Proposez un type pour encoder les arbres enracinés.
  2. Écrivez une fonction prenant deux arbres enracinés, qui les lie avec une nouvelle racine ayant pour fils ces deux arbres.
  3. Écrivez une fonction prenant une liste d'entiers, et qui construit un arbre enraciné dont les feuilles sont les éléments de la liste. L'arbre ainsi construit doit avoir une hauteur de $1$.
  4. Écrivez une fonction qui calcule la somme des feuilles d'un arbre enraciné.
  5. Écrivez une fonction qui calcule le maximum des valeurs des feuilles d'un arbre enraciné.
  6. Pouvez-vous généraliser les deux fonctions précédentes ?
  7. Écrivez une fonction map, qui applique une fonction passée en argument à chaque feuille, et renvoie l'arbre enraciné de même forme avec les images de la fonction en feuilles.
  8. Écrivez une fonction prenant en argument un entier $n$, et qui calcule un arbre binaire complet de hauteur $n$ dont les feuilles sont les entiers de $1$ à $2^n$.
  9. Regroupez le type des arbres enracinés et ses fonctions dans un module.

Exercice 2 : Chaînes de caractères concaténables.

La concaténation de chaînes de caractères est une opération coûteuse, puisqu'il faut réserver de l'espace en mémoire (coût en mémoire) et y recopier les deux chaînes concaténées (coût en temps), qui peuvent être arbitrairement longues. Lors de séries de manipulations de chaînes de caractères, il peut être plus efficace d'adopter une représentation permettant la concaténation de chaînes en temps et mémoire constants (au détriment des temps d'autres opérations, comme l'accès à des caractères).

Une technique classique est d'utiliser des arbres, dont chaque nœud représente la concaténation des chaînes représentées par les sous-arbres, et dont les feuilles stockent des chaînes de caractères élémentaires. On utilise alors le type suivant :

  1. type catstring =
  2. | String of string
  3. | Append of catstring * catstring * int (* (prefix, suffix, length) *)

Pour des raisons d'efficacité, il est utile de garder la longueur de la chaîne correspondant à chaque nœud.

Pour l'exercice, vous aurez besoin des fonctions suivantes :

  1. val String.get : string -> int -> char
  2. val String.length : string -> int (* complexity: O(1) *)
  3. val String.sub : string -> int -> int -> string (* [sub text start len] *)
  4. val String.concat : string list -> string
  1. Écrire une fonction length : catstring -> int.
  2. Proposer une fonction of_string : string -> catstring et une fonction append : catstring -> catstring -> catstring.
  3. Écrire une fonction char_at : int -> catstring -> char option, retournant le caractère d'une position donnée.
  4. Enrichir catstring puis char_at pour pouvoir calculer des préfixes et des suffixes d'une chaîne.
  5. Écrire une fonction de conversion to_string : catstring -> string. Pour cela, vous aurez besoin d'écrire une fonction plus générale, extrayant la sous-chaîne depuis une position et d'une longueur donnée, et ajoutant les facteurs extraits dans une liste, extract : start:int -> len:int -> catstring -> string list -> string list.

Exercice 3 : Listes non-vides.

Proposez un type pour les listes non-vides : une telle liste devra toujours avoir au moins un élément. Pour le reste elle doit se manipuler de la même façon qu'une liste usuelle.

Donnez des définitions pour les fonctions map, reduce, fold, filter.