Cours 4

Htmligure
Version & licenses
Creative Commons License

Algorithmes inductifs

Guyslain
Thursday, 21 July 2016

Retour au sommaire.

Algorithmes inductifs.

Les types inductifs sont des types définis en fonction d'eux-mêmes, par une définition auto-référentielle. Les algorithmes récursifs sont des algorithmes qui sont définis en fonction d'eux-mêmes, leurs définitions sont aussi auto-référentielles. Ce n'est donc pas un hasard si les types inductifs se manipulent particulièrement bien récursivement.

Le principe des algorithmes récursifs est toujours de calculer f v en utilisant le résultat de f v' pour des valeurs v' plus petites que v. Dans le contexte des types inductifs, plus petit signifie structurellement plus petit, v' est plus petit que v si v' est un sous-terme dans la construction de v.

Par exemple, la queue d'une liste est plus petite que la liste elle-même. Le sous-arbre gauche d'un arbre binaire est plus petit que l'arbre binaire tout entier. En reprenant la définition inductive des entiers naturels, on obtient le principe de récurrence, n est plus petit que Succ n.

Reprenons l'exemple des expressions arithmétiques.

  1. type arithm =
  2. | Literal of int
  3. | Sum of arithm * arithm
  4. | Product of arithm * arithm

Donnons une fonction pour évaluer une expression. Lorsqu'on travaille avec un type inductif, et qu'on ne sait pas comment écrire une fonction, un bon réflexe est d'observer la valeur, puis de réfléchir indépendamment à chaque cas :

  1. let eval = function
  2. | Literal i -> ???
  3. | Sum (expr1, exp2) -> ???
  4. | Product (expr1, expr2) -> ???

Dans le premier cas, le résultat doit être la valeur de i. Dans le deuxième, c'est la somme de la valeur de expr1 et de la valeur de expr2. Ces deux valeurs peuvent être obtenues par un appel récursif. Le troisième cas est similaire au deuxième.

  1. let eval = function
  2. | Literal i -> i
  3. | Sum (expr1, exp2) -> eval expr1 + eval expr2
  4. | Product (expr1, expr2) -> eval expr1 * eval expr2

expr1 et expr2 sont dans les deux derniers cas des sous termes de l'expression observée, il est donc valide de rappeler eval récursivement sur ces deux valeurs.

Compliquons un peu l'exercice, en ajoutant des variables aux expressions arithmétiques.

  1. type arithm =
  2. | Variable of string
  3. | Literal of int
  4. | Sum of arithm * arithm
  5. | Product of arithm * arithm

Pour évaluer une telle expression, il faut attribuer une valeur à chaque variable, ce qui nous donne un contexte d'évaluation.

  1. type context = (string * int) list

L'évaluation peut échouer si toutes les variables n'ont pas été définies. Nous allons écrire une fonction qui simplifie l'expression arithmétique autant que possible : si toutes les variables sont définies, on obtiendra un littéral contenant la valeur de l'expression pour ce contexte.

  1. let simplified_sum expr1 expr2 =
  2. match expr1, expr2 with
  3. | Literal 0, expr | expr, Literal 0 -> expr
  4. | Literal i1, Literal i2 -> Literal (i1 + i2)
  5. | _,_ -> Sum (expr1, expr2)

  6. let simplified_product expr1 expr2 =
  7. match expr1, expr2 with
  8. | Literal 0,_ | _, Literal 0 -> Literal 0
  9. | Literal 1, expr | expr, Literal 1 -> expr
  10. | Literal i1, Literal i2 -> Literal (i1 * i2)
  11. | _,_ -> Product (expr1, expr2)

  12. let rec simplify context = function
  13. | Variable x when List.mem_assoc x context -> Literal (List.assoc x context)
  14. | Variable x as unbound_variable -> unbound_variable
  15. | Literal i - > Literal i
  16. | Sum (expr1, expr2) ->
  17. simplified_sum (simplify context expr1) (simplify context expr2)
  18. | Product expr1 expr2 ->
  19. simplified_product (simplify context expr1) (simplify context expr2)

Que fait simplify, en particulier les cas 3 et 4 ? On simplifie les sous-expressions de l'expression à simplifier, et cela par appel récursif à simplify, puisque expr1 et expr2 sont bien des termes plus petits que l'expression observée. Ensuite, on vérifie si localement il est possible de simplifier l'expression, soit en additionnant deux litéraux, soit en les multipliant. On peut même ajouter des règles pour supprimer les additions par zéro, les multiplications par 0 ou 1 . (Notez comment il est possible de filtrer plusieurs cas en leur associant le même calcul).

Retour au sommaire.