Consignes

Vous devrez soumettre votre travail (un unique fichier Caml valide) en utilisant l'interface TPLab.

Le seul fait que votre programme fonctionne ne suffit pas pour avoir une bonne note. Les points suivants seront pris en compte :

  1. la lisibilité de votre programme (choix pertinent pour les noms de variables etc.),
  2. la présence de commentaires aux endroits appropriés,
  3. ...

Attention : les points suivants ne rapportent rien, mais ne pas les respecter pourra retrancher jusqu'à 10 points sur la note finale :

Liens utiles

1. Mémoization

Vous aimeriez optimiser le calcul des valeurs d'une fonction f de la manière suivante : lors du calcul de f x, vous regardez si la valeur correspondante a déjà été calculée.

  1. Si oui, vous renvoyez la valeur déjà calculée
  2. si non, vous calculez f x normalement, et conservez la valeur quelque part.

Cette technique s'appelle "mémoization". Le deuxième calcul de f x est en général plus rapide que le premier...

Utilisez cette méthode pour modifier la définition

  let rec fib1 n =
      if n < 2
      then n
      else fib1 (n-1) + fib1 (n-2)

Pour stocker les résultats, vous pouvez utiliser une liste d'association (les dictionnaires du TD 3) qui contient les paires (n, v)v est la valeur de fib n. La fonction List.assoc : 'a -> ('a*'b) list -> 'b permet de récupérer une valeur v à partir de la valeur n correspondante.

Attention, "List.assoc n table" lève l'exception "Not_found" lorsque "n" n'apparait pas dans "table".

2. Rendre de la monnaie

La boulangère dispose d'une quantité illimitée de pièces de 1, 2, 5 et 10 centimes. Elle peut obtenir n'importe quelle somme à partir de ces pièces, mais comment faire pour minimiser le nombre total de pièces utilisées ?

2.1. Algorithme "glouton"

Proposez un algorithme simple pour trouver une solution optimale. Programmez la fonction correspondante :

monnaie_glouton : int list -> int -> (int*int) list

qui devra prendre en argument la liste des pièces qu'on a le droit d'utiliser et le montant total qu'on veut obtenir. Le résultat sera une solution qui minimise le nombre total de pièces utilisées.

Par exemple :

# monnaie_glouton [1; 2; 5; 10] 9;;
- : (int * int) list = [(5, 1); (2, 2)]

car il suffit d'une pièce de 5 centimes et de 2 pièces de 2 centimes pour faire 9 centimes.

Sur la planète Nethack, les habitants utilisent des pièces de 1, 4 et 6 zorkmid. Faites fonctionner votre fonction sur les entiers de 1 à 10. Que constatez-vous ?

2.2. Algorithme exact récursif

Pour corriger le problème de la question précédente, nous allons utiliser un algorithme récursif plus complexe. Nous allons commencer par seulement calculer le nombre total de pièces utilisées : si les la liste des valeurs de pièces possible est p,

Si on modélise les cas impossibles par un nombre "infini" de pièces, on obtient la fonction récursive suivante :

    compte_monnaie 0 _ = 0
    compte_monnaie _ [] = "+infini"
    compte_monnaie n (p::l) = min (compte_monnaie n l)
                                  (1 + (compte_monnaie (n-p) (p::l)))

Écrivez la fonction compte_monnaie en Caml.

Attention : si vous modélisez "+infini" par "max_int" (le plus grand entier connu par Caml), vous aurez que "max_int + 1 = min_int" ! Pour éviter le problème, vous pouvez utiliser un type option avec

Il faudra alors réécrire les fonctions "min" et "1+...".

2.3. Algorithme exact, mémoizé

Écrivez une version mémoizée de cette fonction.

2.4. Comparaison

Estimez, en fonction de n et de la taille de l, la complexité des fonctions

(Essayez juste de donner un ordre de grandeur...)

Testez expérimentalement ces fonctions :

2.5. Amélioration

Généralisez ce principe pour obtenir le nombre de chacune des pièces utilisées, et écrivez la fonction correspondante :

monnaie_rec : int list -> int -> (int*int) list

Par exemple :

# monnaie_rec [1; 4; 6] 8 ;;
- : (int * int) list = [(2, 4)]