Nous apprenons à utiliser les modules Map et Set. Ce faisant, nous allons écrire un algorithme résolvant le problème du compte est bon, faisant parti du célèbre jeu télévisé Des chiffres et des lettres.
Définir le module de type Set.OrderedType sur les entiers. Son type est :
Pour les questions suivantes, il est utile (mais pas nécessaire) d'utilisation la fonction bind des listes.
Nous allons avoir besoin d'énumérer toutes les sous-listes d'une liste d'entiers, et de faire des opérations dessus. Une sous-liste de $l$ est une liste d'éléments de $l$ apparaissant dans le même ordre (mais pas nécessairement consécutivement).
Coder la fonction sublists correspondante, en utilisant une récurrence sur la queue de la liste. On s'assurera que :
Nous aurons aussi besoin d'énumérer les partitions d'une liste en deux sous-listes (une bipartition). On utilise à nouveau une définition récursive. Toute bipartition d'une liste non-vide s'obtient en ajoutant la tête à une des deux parties d'une bipartition de la queue.
Coder la fonction bipartitions.
Dans le jeu le compte est bon, seules quatre opérations arithmétiques sont autorisés : addition, soustraction, produit et division entière exacte (on ne peut diviser que si le reste est $0$). De plus, les résultats négatifs ou nuls sont interdits.
On définit le type algébrique :
Donner une fonction valid_combination op a b qui prend un opérateur op et deux expressions arithmétiques, et qui décide si cet opérateur peut être appliqué à $a$ et $b$. Il s'agit par exemple de vérifier que les divisions sont entières ou que les soustractions donnent des valeurs positives.
En déduire une fonction qui prenant deux expressions, calcule toutes les expressions valides pouvant être construites à partir de ces deux expressions en utilisant un opérateur.
Pour une liste $l$ d'entiers, nous allons calculer pour tout entier $u$ s'il existe une expression $\text{expr}_u$ arithmétique utilisant $l$ comme litéraux, et s'évaluant en $u$. Nous allons stocké toutes ces expressions dans une application partielle, associant $\text{expr}_u$ à l'entier $u$, dès qu'une telle expression existe. Avant de voir comment faire cela, nous allons juste écrire une fonction ajoutant un lien $(u,\text{expr}_u)$ dans notre stock.
Pour cela, on écrit la fonction suivante, qui considère une expression arithmétique, et l'ajoute dans l'application partielle. Comme il peut y avoir une expression de même valeur déjà stockée, on vérifiera si c'est le cas, et si oui on gardera seulement l'expression de plus petit score.
Nous voulons maintenant tester toutes les expressions possibles avec une liste $l$. Nous procédons par induction sur la longueur de la liste.
Le principe de l'algorithme est le suivant. Étant donné une liste d'entiers $l$ avec au moins 2 éléments (sinon c'est facile), on calcule toutes les bipartitions de cette liste. Pour chaque bipartition, on calcule récursivement les solutions pour les deux parties (on obtient deux maps), puis en utilisant all_pairs et combine, on génère toutes les expressions arithmétiques pour cette bipartition. Chaque expression pour chaque bipartition est ajoutée avec expr_imap_add à une application partielle codant la solution pour $l$.
Écrire l'algorithme correspondant. Le tester.
La suite du TP est optionnelle.
Lorsque nous prenons les sous-listes des sous-listes, nous retombons plusieurs fois sur les mêmes listes. Notre calcul n'est donc pas efficace puisque nous recalculons les solutions pour chaque sous-liste plusieurs fois.
Examinons le code suivant.
Il s'agit d'une version mémoïsée de la fonction de Fibonacci. Puisqu'on ne dispose pas de mémoire à proprement parler, on garde une table d'association entre les entiers et leurs images par Fibonacci en argument de la fonction. Dès qu'on calcule une nouvelle valeur, on l'ajoute dans la table.
Nous pouvons faire la même chose avec les sous-listes. Ajouter en argument une map sur les sous-listes, qui associe à chaque sous-liste une map des entiers vers les expressions arithmétiques construites sur cette sous-liste.
Une instance est composée d'un objectif et de six entiers choisis parmi la liste
$$[ 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 25; 50; 75; 100]$$
L'objectif est un entier entre 100 et 999 , tiré uniformément aléatoirement.
Utiliser le module Random pour générer des instances aléatoires.
Vous pouvez ensuite tenter d'améliorer le programme. Par exemple :