<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
	<id>http://os-vps418.infomaniak.ch:1250/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dornel</id>
	<title>Wiki du LAMA (UMR 5127) - Contributions [fr]</title>
	<link rel="self" type="application/atom+xml" href="http://os-vps418.infomaniak.ch:1250/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dornel"/>
	<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php/Sp%C3%A9cial:Contributions/Dornel"/>
	<updated>2026-05-21T11:28:10Z</updated>
	<subtitle>Contributions</subtitle>
	<generator>MediaWiki 1.39.4</generator>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12552</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12552"/>
		<updated>2020-05-29T09:02:22Z</updated>

		<summary type="html">&lt;p&gt;Dornel : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|150px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Code de la structure tas gaucher&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Comme il a été démontré précédemment que la longueur de la colonne vertébrale est au plus logarithmique, on peut en déduire que merge a un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur. Et puisque le temps d&#039;exécution de merge est d&#039;ordre O(log n), il en va de même pour insert.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants. De plus, merge ayant un temps d&#039;exécution d&#039;ordre O(log n), celui de deleteMin est le même.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
[[File:Schema_3.png|300px|thumb|right|Représentation des arbres binaires de rangs 0 à 3]]&lt;br /&gt;
&lt;br /&gt;
Les tas binomiaux sont des tas composés d&#039;un type plus primitif appelé arbre binomial. Les arbres binomiaux sont défini inductivement comme suit :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang 0 est un singleton de nœud ;&lt;br /&gt;
* Un arbre binomial de rang r est formé en liant deux arbres binomiaux de rang r-1 tel qu&#039;un des arbres devient l&#039;enfant le plus à gauche de l&#039;autre.&lt;br /&gt;
&lt;br /&gt;
De cette définition, on comprend qu&#039;un arbre binomial de rang r contient exactement 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; nœuds.&lt;br /&gt;
&lt;br /&gt;
Il existe une deuxième définition équivalente des arbres binomiaux :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang r est un nœud ayant r enfants t&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; à t&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt; où chaque enfant t&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; est un arbre binomial de rang r - i.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, on représente un nœud dans un arbre binomial comme un élément avec une liste de ses enfants. Par commodité, on note aussi le rang de chaque nœud.&lt;br /&gt;
&lt;br /&gt;
De plus, un tas binomial étant une liste d&#039;arbres binomiaux ne pouvant comporter qu&#039;un seul arbre d&#039;un rang donné et un arbre binomial de rang r comportant 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; éléments, on constate alors que les arbres d&#039;un tas binomial de taille n correspondent à la représentation binaire de n.&lt;br /&gt;
&lt;br /&gt;
Par exemple, la représentation binaire de 13 est 1101, donc un tas binomial de taille 13 contient un arbre de rang 0, un de rang 2 et un de rang 3 (de taille respective 1, 4 et 8).&lt;br /&gt;
De plus, comme la représentation binaire de n contient au plus ⌊log(n + 1)⌋ bits à 1, un tas binomial de taille n contient au plus ⌊log(n + 1)⌋ arbres.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BinomialHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = Node of (int * elem * tree list)&lt;br /&gt;
  type heap = tree list&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let rank (Node(r, _, _)) = r&lt;br /&gt;
  let root (Node(_, x, _)) = x&lt;br /&gt;
&lt;br /&gt;
  let link = ...&lt;br /&gt;
&lt;br /&gt;
  let rec insTree...&lt;br /&gt;
&lt;br /&gt;
  let insert = ...&lt;br /&gt;
&lt;br /&gt;
  let rec merge...&lt;br /&gt;
&lt;br /&gt;
  let rec removeMinTree...&lt;br /&gt;
&lt;br /&gt;
  let findMin = ...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin = ...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Les fonctions que nous allons étudier ici sont link, insert, merge, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;link - Lie deux arbres de même rang pour en construire un de rang supérieur&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les arbres étant triés en respectant la propriété du tas, il suffit de comparer les racines pour voir lequel reste au sommet en les liant, l&#039;autre étant ajouté à la liste des enfants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let link = fun (Node(r, x1, c1) as t1) (Node(_, x2, c2) as t2) -&amp;gt;&lt;br /&gt;
  if (O.leq x1 x2)&lt;br /&gt;
  then Node(r + 1, x1, t2 :: c1)&lt;br /&gt;
  else Node(r + 1, x2, t1 :: c2)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insertion d&#039;un élément&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les fonctions insert et merge sont toutes deux vaguement comparables à l&#039;addition de deux nombres binaires. Pour insérer un nouvel élément, on crée un singleton d&#039;arbre (donc de rang 0) puis on parcourt tous les arbres du tas par ordre croissant de rang jusqu&#039;à trouver un rang manquant en liant les arbres de rang égal en chemin. On peut comparer le lien à une retenue arithmétique.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insTree = fun t ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; [t]&lt;br /&gt;
  | t&#039; :: ts&#039; as ts -&amp;gt; if rank t &amp;lt; rank t&#039;&lt;br /&gt;
                       then t :: ts&lt;br /&gt;
                       else insTree (link t t&#039;) ts&#039;&lt;br /&gt;
&lt;br /&gt;
let insert = fun x ts -&amp;gt; insTree (Node(0, x, [])) ts&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le pire scénario étant dans un tas de taille n = 2&amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; - 1 qui nécessiterait k liens, donnant un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusion de deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, merge parcourt la liste d&#039;arbres de chaque tas par ordre croissant de rang en liant les arbres de rang égal.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec merge = fun ts1 ts2 -&amp;gt;&lt;br /&gt;
  match ts1, ts2 with&lt;br /&gt;
  | _, [] -&amp;gt; ts1&lt;br /&gt;
  | [], _ -&amp;gt; ts2&lt;br /&gt;
  | t1 :: ts1&#039;, t2 :: ts2&#039; -&amp;gt; if rank t1 &amp;lt; rank t2&lt;br /&gt;
                              then t1 :: (merge ts1&#039; ts2)&lt;br /&gt;
                              else if rank t2 &amp;lt; rank t1&lt;br /&gt;
                                   then t2 :: (merge ts1 ts2&#039;)&lt;br /&gt;
                                   else insTree (link t1 t2) (merge ts1&#039; ts2&#039;)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin et deleteMin - Trouve et supprime le minimum respectivement&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ces deux fonctions ont besoin de la même fonction auxiliaire, à savoir removeMinTree, qui trouve l&#039;arbre avec la plus petite valeur et l&#039;extrait de la liste, retournant à la fois l&#039;arbre et le reste de la liste.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec removeMinTree = fun ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | [t] -&amp;gt; (t, [])&lt;br /&gt;
  | t :: ts -&amp;gt; let (t&#039;, ts&#039;) = removeMinTree ts in&lt;br /&gt;
               if (O.leq (root t) (root t&#039;))&lt;br /&gt;
               then (t, ts)&lt;br /&gt;
               else (t&#039;, t :: ts&#039;)&lt;br /&gt;
&lt;br /&gt;
let findMin = fun ts -&amp;gt; let (t, _) = removeMinTree ts in root t&lt;br /&gt;
&lt;br /&gt;
let deleteMin = fun ts -&amp;gt; let (Node(_, x, ts1), ts2) = removeMinTree ts in merge (rev ts1) ts2&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
À partir de là, findMin retourne juste la racine de l&#039;arbre. Cependant, deleteMin est plus complexe. En effet, après avoir retiré la racine de l&#039;arbre, il faut remettre la liste d&#039;enfants de l&#039;arbre avec le reste de la liste des arbres. Cela dit, bien que chaque liste d&#039;enfants est une collection d&#039;arbres binomiaux de rang unique, ceux-ci sont triés &#039;&#039;par ordre décroissant de rang et non par ordre croissant&#039;&#039;. Afin de convertir cette liste en tas binomial valide, on l&#039;inverse à l&#039;aide de la fonction rev avant de la fusionner avec les arbres restants.&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
[[File:Schema_4.png|300px|thumb|right|Illustration des invariants]]&lt;br /&gt;
&lt;br /&gt;
Précédemment, nous avons vu les arbres de recherche binaires. Bien que cette structure fonctionne bien avec des données aléatoires ou désordonnées, leur performance est grandement réduite avec des données ordonnées car toutes les opérations ont un temps d&#039;exécution d&#039;ordre allant jusqu&#039;à O(n). Une solution à ce problème est de garder chaque arbre relativement équilibré. Ainsi, aucune opération n&#039;a de temps d&#039;exécution d&#039;ordre excédant O(log n). Une famille d&#039;arbres de recherche binaires équilibrés populaire est celle des &#039;&#039;&#039;arbres bicolores, ou arbres rouge-noir&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Un arbre bicolore est un arbre binaire dont chaque nœud a une couleur, rouge ou noir. On considère qu&#039;un nœud vide est noir.&lt;br /&gt;
De plus, un arbre bicolore doit respecter deux invariants :&lt;br /&gt;
# &#039;&#039;&#039;Aucun nœud rouge ne peut avoir d&#039;enfant rouge.&#039;&#039;&#039;&lt;br /&gt;
# &#039;&#039;&#039;Toutes les branches allant de la racine à une feuille doivent avoir le même nombre de nœuds noirs.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
De ces deux invariants, on peut extraire que le chemin le plus long - alternant nœuds noirs et rouge - est au plus deux fois plus long que le chemin le plus court - composé uniquement de nœuds noirs.&lt;br /&gt;
En reprenant la démonstration faite sur la longueur de la colonne vertébrale d&#039;un tas gaucher, on sait que le chemin le plus court d&#039;un arbre de profondeur n contient au plus ⌊log(n + 1)⌋ éléments. Par conséquent, on peut en déduire que le chemin le plus long contient au plus 2⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module RedBlackTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type color = R | B&lt;br /&gt;
  type tree = E | T of (color * tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let balance...&lt;br /&gt;
&lt;br /&gt;
  let rec member = fun x t -&amp;gt;&lt;br /&gt;
    match t with&lt;br /&gt;
    | E -&amp;gt; false&lt;br /&gt;
    | T(_, e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then member x a&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then member x b&lt;br /&gt;
                              else true&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions insert et balance. Mis à part le caractère générique pour la couleur, member fonctionne comme pour les arbres binaires non équilibrés.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insertion d&#039;une nouvelle valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
La différence majeure entre cette fonction et sa variante pour les arbres non équilibrés relève du respect des invariants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun x t -&amp;gt;&lt;br /&gt;
  let fun ins = function E -&amp;gt; T(R, E, x, E)&lt;br /&gt;
                       | T(color, e1, y, e2) as t&#039; -&amp;gt; if (O.lt x y)&lt;br /&gt;
                                                      then balance (color, ins a, y, b)&lt;br /&gt;
                                                      else if (O.lt y x)&lt;br /&gt;
                                                      then balance (color, a, y, ins b)&lt;br /&gt;
                                                      else t&#039;&lt;br /&gt;
  in let T(_, e1, y, e2) = ins t&lt;br /&gt;
  in T(B, e1, y, e2)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette fonction étend la variante non équilibrée sur trois axes majeurs :&lt;br /&gt;
Premièrement, quand on crée un nouveau nœud, on le colore initialement en rouge. Deuxièmement, on force le nœud final à être noir, indépendamment de la couleur retournée par la fonction ins. Enfin, on replace les appels au constructeur T par des appels à la fonction balance, chargée de construire un nœud tout en modifiant l&#039;arbre pour respecter les invariants d&#039;équilibre.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;balance - Équilibrage de l&#039;arbre selon les invariants&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En donnant la couleur rouge à un nouveau nœud, on maintient l&#039;invariant 2 mais viole l&#039;invariant 1 à chaque fois que le parent est rouge. Pour ce faire, on autorise une violation rouge-rouge à la fois que l&#039;on fait remonter le long de la branche de recherche vers la racine en équilibrant.&lt;br /&gt;
La fonction balance détecte une violation rouge-rouge quand il voit une suite noir-rouge-rouge et la corrige (voir figure ci-dessous).&lt;br /&gt;
&lt;br /&gt;
[[File:Schema_5.png|none|500px|thumb|left|Fonctionnement de la fonction balance]]&lt;br /&gt;
&lt;br /&gt;
Ici, le code essaie de trouver l&#039;un des quatre cas noir-rouge-rouge ne respectant pas l&#039;invariant 1, puis de le corriger. Si aucun cas n&#039;est trouvé, alors on construit le sous-arbre tel quel.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let balance = function&lt;br /&gt;
    (B,T (R,T (R,a,x,b),y,c),z,d) -&amp;gt; T (R,T (B,a,x,b),y,T (B,c,z,d))&lt;br /&gt;
  | (B,T (R,a,x,T (R,b,y,c)),z,d) -&amp;gt; T (R,T (B,a,x,b),y,T (B,c,z,d))&lt;br /&gt;
  | (B,a,x,T (R,T (R,b,y,c),z,d)) -&amp;gt; T (R,T (B,a,x,b),y,T (B,c,z,d))&lt;br /&gt;
  | (B,a,x,T (R,b,y,T(R,c,z,d))) -&amp;gt; T (R,T (B,a,x,b),y,T (B,c,z,d))&lt;br /&gt;
  | (c,a,x,b) -&amp;gt; T (c,a,x,b)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
Après s&#039;être penché en détail sur la thèse d&#039;Okasaki, voici quelques articles plus récents en rapport avec le sujet permettant d&#039;élargir la question.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Manufacturing Datatypes&#039;&#039;&#039; par Ralf Hinze, publié en 1999 et édité par Okasaki traitant d&#039;une structure générale pour construire des structures de données purement fonctionnelles permettant de satisfaire des contraintes de taille ou de forme, notamment pour l&#039;implémentation de matrices et de certains types d&#039;arbres.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Scrap your boilerplate: a practical design pattern for generic programming&#039;&#039;&#039; de Ralf Lämmel et Simon Peyton-Jones proposant un modèle de métastructure, c&#039;est-à-dire (une structure permettant de générer du code ?), afin de remédier à l&#039;usage de code passe-partout, autrement dit du code répété à plusieurs reprises avec très peu de modifications.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Finger trees: a simple general-purpose data structure&#039;&#039;&#039; de Ralf Hinze et Ross Paterson introduisant une nouvelle structure : les arbres pointés 2-3 permettant d&#039;accéder aux feuilles dans un laps de temps amorti constant. Cette structure peut servir de base pour plusieurs autres structures de données.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parallel Concurrent ML&#039;&#039;&#039; de John Reppy, Claudio V. Russo et Yingqi Xiao présentant le langage fonctionnel concurrent CML (pour Concurrent ML), c&#039;est-à-dire que le programme peut utiliser plusieurs processus simultanément, réduisant le temps d&#039;exécution.&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia FR)]&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Leftist_tree Tas gaucher (Wikipédia EN)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.39.5334&amp;amp;rep=rep1&amp;amp;type=pdf#page=7 Manufacturing Datatypes]&lt;br /&gt;
&lt;br /&gt;
* [https://www.researchgate.net/publication/221282345_Scrap_Your_Boilerplate_A_Practical_Design_Pattern_for_Generic_Programming Scrap your boilerplate: a practical design pattern for generic programming]&lt;br /&gt;
&lt;br /&gt;
* [https://archive.alvb.in/msc/03_infoafp/papers/2012-12-18_WerkCollege_FingerTreesRalfHinze.pdf Finger trees: a simple general-purpose data structure]&lt;br /&gt;
&lt;br /&gt;
* [https://www.microsoft.com/en-us/research/wp-content/uploads/2009/09/Parallel-Concurrent-ML.pdf Parallel Concurrent ML]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_5.png&amp;diff=12551</id>
		<title>Fichier:Schema 5.png</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_5.png&amp;diff=12551"/>
		<updated>2020-05-29T08:55:55Z</updated>

		<summary type="html">&lt;p&gt;Dornel : Illustration du fonctionnement de la fonction balance&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Illustration du fonctionnement de la fonction balance&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_4.png&amp;diff=12550</id>
		<title>Fichier:Schema 4.png</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_4.png&amp;diff=12550"/>
		<updated>2020-05-29T08:03:51Z</updated>

		<summary type="html">&lt;p&gt;Dornel : Illustration des invariants des arbres bicolores&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Illustration des invariants des arbres bicolores&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12549</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12549"/>
		<updated>2020-05-29T07:45:06Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Arbre rouge / noir */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|150px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Code de la structure tas gaucher&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Comme il a été démontré précédemment que la longueur de la colonne vertébrale est au plus logarithmique, on peut en déduire que merge a un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur. Et puisque le temps d&#039;exécution de merge est d&#039;ordre O(log n), il en va de même pour insert.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants. De plus, merge ayant un temps d&#039;exécution d&#039;ordre O(log n), celui de deleteMin est le même.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
[[File:Schema_3.png|500px|thumb|right|Représentation des arbres binaires de rangs 0 à 3]]&lt;br /&gt;
&lt;br /&gt;
Les tas binomiaux sont des tas composés d&#039;un type plus primitif appelé arbre binomial. Les arbres binomiaux sont défini inductivement comme suit :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang 0 est un singleton de nœud ;&lt;br /&gt;
* Un arbre binomial de rang r est formé en liant deux arbres binomiaux de rang r-1 tel qu&#039;un des arbres devient l&#039;enfant le plus à gauche de l&#039;autre.&lt;br /&gt;
&lt;br /&gt;
De cette définition, on comprend qu&#039;un arbre binomial de rang r contient exactement 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; nœuds.&lt;br /&gt;
&lt;br /&gt;
Il existe une deuxième définition équivalente des arbres binomiaux :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang r est un nœud ayant r enfants t&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; à t&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt; où chaque enfant t&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; est un arbre binomial de rang r - i.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, on représente un nœud dans un arbre binomial comme un élément avec une liste de ses enfants. Par commodité, on note aussi le rang de chaque nœud.&lt;br /&gt;
&lt;br /&gt;
De plus, un tas binomial étant une liste d&#039;arbres binomiaux ne pouvant comporter qu&#039;un seul arbre d&#039;un rang donné et un arbre binomial de rang r comportant 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; éléments, on constate alors que les arbres d&#039;un tas binomial de taille n correspondent à la représentation binaire de n.&lt;br /&gt;
&lt;br /&gt;
Par exemple, la représentation binaire de 13 est 1101, donc un tas binomial de taille 13 contient un arbre de rang 0, un de rang 2 et un de rang 3 (de taille respective 1, 4 et 8).&lt;br /&gt;
De plus, comme la représentation binaire de n contient au plus ⌊log(n + 1)⌋ bits à 1, un tas binomial de taille n contient au plus ⌊log(n + 1)⌋ arbres.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BinomialHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = Node of (int * elem * tree list)&lt;br /&gt;
  type heap = tree list&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let rank (Node(r, _, _)) = r&lt;br /&gt;
  let root (Node(_, x, _)) = x&lt;br /&gt;
&lt;br /&gt;
  let link = ...&lt;br /&gt;
&lt;br /&gt;
  let rec insTree...&lt;br /&gt;
&lt;br /&gt;
  let insert = ...&lt;br /&gt;
&lt;br /&gt;
  let rec merge...&lt;br /&gt;
&lt;br /&gt;
  let rec removeMinTree...&lt;br /&gt;
&lt;br /&gt;
  let findMin = ...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin = ...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Les fonctions que nous allons étudier ici sont link, insert, merge, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;link - Lie deux arbres de même rang pour en construire un de rang supérieur&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les arbres étant triés en respectant la propriété du tas, il suffit de comparer les racines pour voir lequel reste au sommet en les liant, l&#039;autre étant ajouté à la liste des enfants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let link = fun (Node(r, x1, c1) as t1) (Node(_, x2, c2) as t2) -&amp;gt;&lt;br /&gt;
  if (O.leq x1 x2)&lt;br /&gt;
  then Node(r + 1, x1, t2 :: c1)&lt;br /&gt;
  else Node(r + 1, x2, t1 :: c2)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insertion d&#039;un élément&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les fonctions insert et merge sont toutes deux vaguement comparables à l&#039;addition de deux nombres binaires. Pour insérer un nouvel élément, on crée un singleton d&#039;arbre (donc de rang 0) puis on parcourt tous les arbres du tas par ordre croissant de rang jusqu&#039;à trouver un rang manquant en liant les arbres de rang égal en chemin. On peut comparer le lien à une retenue arithmétique.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insTree = fun t ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; [t]&lt;br /&gt;
  | t&#039; :: ts&#039; as ts -&amp;gt; if rank t &amp;lt; rank t&#039;&lt;br /&gt;
                       then t :: ts&lt;br /&gt;
                       else insTree (link t t&#039;) ts&#039;&lt;br /&gt;
&lt;br /&gt;
let insert = fun x ts -&amp;gt; insTree (Node(0, x, [])) ts&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le pire scénario étant dans un tas de taille n = 2&amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; - 1 qui nécessiterait k liens, donnant un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusion de deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, merge parcourt la liste d&#039;arbres de chaque tas par ordre croissant de rang en liant les arbres de rang égal.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec merge = fun ts1 ts2 -&amp;gt;&lt;br /&gt;
  match ts1, ts2 with&lt;br /&gt;
  | _, [] -&amp;gt; ts1&lt;br /&gt;
  | [], _ -&amp;gt; ts2&lt;br /&gt;
  | t1 :: ts1&#039;, t2 :: ts2&#039; -&amp;gt; if rank t1 &amp;lt; rank t2&lt;br /&gt;
                              then t1 :: (merge ts1&#039; ts2)&lt;br /&gt;
                              else if rank t2 &amp;lt; rank t1&lt;br /&gt;
                                   then t2 :: (merge ts1 ts2&#039;)&lt;br /&gt;
                                   else insTree (link t1 t2) (merge ts1&#039; ts2&#039;)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin et deleteMin - Trouve et supprime le minimum respectivement&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ces deux fonctions ont besoin de la même fonction auxiliaire, à savoir removeMinTree, qui trouve l&#039;arbre avec la plus petite valeur et l&#039;extrait de la liste, retournant à la fois l&#039;arbre et le reste de la liste.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec removeMinTree = fun ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | [t] -&amp;gt; (t, [])&lt;br /&gt;
  | t :: ts -&amp;gt; let (t&#039;, ts&#039;) = removeMinTree ts in&lt;br /&gt;
               if (O.leq (root t) (root t&#039;))&lt;br /&gt;
               then (t, ts)&lt;br /&gt;
               else (t&#039;, t :: ts&#039;)&lt;br /&gt;
&lt;br /&gt;
let findMin = fun ts -&amp;gt; let (t, _) = removeMinTree ts in root t&lt;br /&gt;
&lt;br /&gt;
let deleteMin = fun ts -&amp;gt; let (Node(_, x, ts1), ts2) = removeMinTree ts in merge (rev ts1) ts2&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
À partir de là, findMin retourne juste la racine de l&#039;arbre. Cependant, deleteMin est plus complexe. En effet, après avoir retiré la racine de l&#039;arbre, il faut remettre la liste d&#039;enfants de l&#039;arbre avec le reste de la liste des arbres. Cela dit, bien que chaque liste d&#039;enfants est une collection d&#039;arbres binomiaux de rang unique, ceux-ci sont triés &#039;&#039;par ordre décroissant de rang et non par ordre croissant&#039;&#039;. Afin de convertir cette liste en tas binomial valide, on l&#039;inverse à l&#039;aide de la fonction rev avant de la fusionner avec les arbres restants.&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
Précédemment, nous avons vu les arbres de recherche binaires. Bien que cette structure fonctionne bien avec des données aléatoires ou désordonnées, leur performance est grandement réduite avec des données ordonnées car toutes les opérations ont un temps d&#039;exécution d&#039;ordre allant jusqu&#039;à O(n). Une solution à ce problème est de garder chaque arbre relativement équilibré. Ainsi, aucune opération n&#039;a de temps d&#039;exécution d&#039;ordre excédant O(log n). Une famille d&#039;arbres de recherche binaires équilibrés populaire est celle des arbres bicolores, ou arbres rouge-noir.&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
Après s&#039;être penché en détail sur la thèse d&#039;Okasaki, voici quelques articles plus récents en rapport avec le sujet permettant d&#039;élargir la question.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Manufacturing Datatypes&#039;&#039;&#039; par Ralf Hinze, publié en 1999 et édité par Okasaki traitant d&#039;une structure générale pour construire des structures de données purement fonctionnelles permettant de satisfaire des contraintes de taille ou de forme, notamment pour l&#039;implémentation de matrices et de certains types d&#039;arbres.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Scrap your boilerplate: a practical design pattern for generic programming&#039;&#039;&#039; de Ralf Lämmel et Simon Peyton-Jones proposant un modèle de métastructure, c&#039;est-à-dire (une structure permettant de générer du code ?), afin de remédier à l&#039;usage de code passe-partout, autrement dit du code répété à plusieurs reprises avec très peu de modifications.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Finger trees: a simple general-purpose data structure&#039;&#039;&#039; de Ralf Hinze et Ross Paterson introduisant une nouvelle structure : les arbres pointés 2-3 permettant d&#039;accéder aux feuilles dans un laps de temps amorti constant. Cette structure peut servir de base pour plusieurs autres structures de données.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parallel Concurrent ML&#039;&#039;&#039; de John Reppy, Claudio V. Russo et Yingqi Xiao présentant le langage fonctionnel concurrent CML (pour Concurrent ML), c&#039;est-à-dire que le programme peut utiliser plusieurs processus simultanément, réduisant le temps d&#039;exécution.&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia FR)]&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Leftist_tree Tas gaucher (Wikipédia EN)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.39.5334&amp;amp;rep=rep1&amp;amp;type=pdf#page=7 Manufacturing Datatypes]&lt;br /&gt;
&lt;br /&gt;
* [https://www.researchgate.net/publication/221282345_Scrap_Your_Boilerplate_A_Practical_Design_Pattern_for_Generic_Programming Scrap your boilerplate: a practical design pattern for generic programming]&lt;br /&gt;
&lt;br /&gt;
* [https://archive.alvb.in/msc/03_infoafp/papers/2012-12-18_WerkCollege_FingerTreesRalfHinze.pdf Finger trees: a simple general-purpose data structure]&lt;br /&gt;
&lt;br /&gt;
* [https://www.microsoft.com/en-us/research/wp-content/uploads/2009/09/Parallel-Concurrent-ML.pdf Parallel Concurrent ML]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12548</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12548"/>
		<updated>2020-05-28T14:43:35Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Sources et annexes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|150px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Code de la structure tas gaucher&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Comme il a été démontré précédemment que la longueur de la colonne vertébrale est au plus logarithmique, on peut en déduire que merge a un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur. Et puisque le temps d&#039;exécution de merge est d&#039;ordre O(log n), il en va de même pour insert.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants. De plus, merge ayant un temps d&#039;exécution d&#039;ordre O(log n), celui de deleteMin est le même.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
[[File:Schema_3.png|500px|thumb|right|Représentation des arbres binaires de rangs 0 à 3]]&lt;br /&gt;
&lt;br /&gt;
Les tas binomiaux sont des tas composés d&#039;un type plus primitif appelé arbre binomial. Les arbres binomiaux sont défini inductivement comme suit :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang 0 est un singleton de nœud ;&lt;br /&gt;
* Un arbre binomial de rang r est formé en liant deux arbres binomiaux de rang r-1 tel qu&#039;un des arbres devient l&#039;enfant le plus à gauche de l&#039;autre.&lt;br /&gt;
&lt;br /&gt;
De cette définition, on comprend qu&#039;un arbre binomial de rang r contient exactement 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; nœuds.&lt;br /&gt;
&lt;br /&gt;
Il existe une deuxième définition équivalente des arbres binomiaux :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang r est un nœud ayant r enfants t&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; à t&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt; où chaque enfant t&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; est un arbre binomial de rang r - i.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, on représente un nœud dans un arbre binomial comme un élément avec une liste de ses enfants. Par commodité, on note aussi le rang de chaque nœud.&lt;br /&gt;
&lt;br /&gt;
De plus, un tas binomial étant une liste d&#039;arbres binomiaux ne pouvant comporter qu&#039;un seul arbre d&#039;un rang donné et un arbre binomial de rang r comportant 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; éléments, on constate alors que les arbres d&#039;un tas binomial de taille n correspondent à la représentation binaire de n.&lt;br /&gt;
&lt;br /&gt;
Par exemple, la représentation binaire de 13 est 1101, donc un tas binomial de taille 13 contient un arbre de rang 0, un de rang 2 et un de rang 3 (de taille respective 1, 4 et 8).&lt;br /&gt;
De plus, comme la représentation binaire de n contient au plus ⌊log(n + 1)⌋ bits à 1, un tas binomial de taille n contient au plus ⌊log(n + 1)⌋ arbres.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BinomialHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = Node of (int * elem * tree list)&lt;br /&gt;
  type heap = tree list&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let rank (Node(r, _, _)) = r&lt;br /&gt;
  let root (Node(_, x, _)) = x&lt;br /&gt;
&lt;br /&gt;
  let link = ...&lt;br /&gt;
&lt;br /&gt;
  let rec insTree...&lt;br /&gt;
&lt;br /&gt;
  let insert = ...&lt;br /&gt;
&lt;br /&gt;
  let rec merge...&lt;br /&gt;
&lt;br /&gt;
  let rec removeMinTree...&lt;br /&gt;
&lt;br /&gt;
  let findMin = ...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin = ...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Les fonctions que nous allons étudier ici sont link, insert, merge, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;link - Lie deux arbres de même rang pour en construire un de rang supérieur&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les arbres étant triés en respectant la propriété du tas, il suffit de comparer les racines pour voir lequel reste au sommet en les liant, l&#039;autre étant ajouté à la liste des enfants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let link = fun (Node(r, x1, c1) as t1) (Node(_, x2, c2) as t2) -&amp;gt;&lt;br /&gt;
  if (O.leq x1 x2)&lt;br /&gt;
  then Node(r + 1, x1, t2 :: c1)&lt;br /&gt;
  else Node(r + 1, x2, t1 :: c2)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insertion d&#039;un élément&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les fonctions insert et merge sont toutes deux vaguement comparables à l&#039;addition de deux nombres binaires. Pour insérer un nouvel élément, on crée un singleton d&#039;arbre (donc de rang 0) puis on parcourt tous les arbres du tas par ordre croissant de rang jusqu&#039;à trouver un rang manquant en liant les arbres de rang égal en chemin. On peut comparer le lien à une retenue arithmétique.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insTree = fun t ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; [t]&lt;br /&gt;
  | t&#039; :: ts&#039; as ts -&amp;gt; if rank t &amp;lt; rank t&#039;&lt;br /&gt;
                       then t :: ts&lt;br /&gt;
                       else insTree (link t t&#039;) ts&#039;&lt;br /&gt;
&lt;br /&gt;
let insert = fun x ts -&amp;gt; insTree (Node(0, x, [])) ts&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le pire scénario étant dans un tas de taille n = 2&amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; - 1 qui nécessiterait k liens, donnant un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusion de deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, merge parcourt la liste d&#039;arbres de chaque tas par ordre croissant de rang en liant les arbres de rang égal.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec merge = fun ts1 ts2 -&amp;gt;&lt;br /&gt;
  match ts1, ts2 with&lt;br /&gt;
  | _, [] -&amp;gt; ts1&lt;br /&gt;
  | [], _ -&amp;gt; ts2&lt;br /&gt;
  | t1 :: ts1&#039;, t2 :: ts2&#039; -&amp;gt; if rank t1 &amp;lt; rank t2&lt;br /&gt;
                              then t1 :: (merge ts1&#039; ts2)&lt;br /&gt;
                              else if rank t2 &amp;lt; rank t1&lt;br /&gt;
                                   then t2 :: (merge ts1 ts2&#039;)&lt;br /&gt;
                                   else insTree (link t1 t2) (merge ts1&#039; ts2&#039;)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin et deleteMin - Trouve et supprime le minimum respectivement&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ces deux fonctions ont besoin de la même fonction auxiliaire, à savoir removeMinTree, qui trouve l&#039;arbre avec la plus petite valeur et l&#039;extrait de la liste, retournant à la fois l&#039;arbre et le reste de la liste.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec removeMinTree = fun ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | [t] -&amp;gt; (t, [])&lt;br /&gt;
  | t :: ts -&amp;gt; let (t&#039;, ts&#039;) = removeMinTree ts in&lt;br /&gt;
               if (O.leq (root t) (root t&#039;))&lt;br /&gt;
               then (t, ts)&lt;br /&gt;
               else (t&#039;, t :: ts&#039;)&lt;br /&gt;
&lt;br /&gt;
let findMin = fun ts -&amp;gt; let (t, _) = removeMinTree ts in root t&lt;br /&gt;
&lt;br /&gt;
let deleteMin = fun ts -&amp;gt; let (Node(_, x, ts1), ts2) = removeMinTree ts in merge (rev ts1) ts2&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
À partir de là, findMin retourne juste la racine de l&#039;arbre. Cependant, deleteMin est plus complexe. En effet, après avoir retiré la racine de l&#039;arbre, il faut remettre la liste d&#039;enfants de l&#039;arbre avec le reste de la liste des arbres. Cela dit, bien que chaque liste d&#039;enfants est une collection d&#039;arbres binomiaux de rang unique, ceux-ci sont triés &#039;&#039;par ordre décroissant de rang et non par ordre croissant&#039;&#039;. Afin de convertir cette liste en tas binomial valide, on l&#039;inverse à l&#039;aide de la fonction rev avant de la fusionner avec les arbres restants.&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
Après s&#039;être penché en détail sur la thèse d&#039;Okasaki, voici quelques articles plus récents en rapport avec le sujet permettant d&#039;élargir la question.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Manufacturing Datatypes&#039;&#039;&#039; par Ralf Hinze, publié en 1999 et édité par Okasaki traitant d&#039;une structure générale pour construire des structures de données purement fonctionnelles permettant de satisfaire des contraintes de taille ou de forme, notamment pour l&#039;implémentation de matrices et de certains types d&#039;arbres.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Scrap your boilerplate: a practical design pattern for generic programming&#039;&#039;&#039; de Ralf Lämmel et Simon Peyton-Jones proposant un modèle de métastructure, c&#039;est-à-dire (une structure permettant de générer du code ?), afin de remédier à l&#039;usage de code passe-partout, autrement dit du code répété à plusieurs reprises avec très peu de modifications.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Finger trees: a simple general-purpose data structure&#039;&#039;&#039; de Ralf Hinze et Ross Paterson introduisant une nouvelle structure : les arbres pointés 2-3 permettant d&#039;accéder aux feuilles dans un laps de temps amorti constant. Cette structure peut servir de base pour plusieurs autres structures de données.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parallel Concurrent ML&#039;&#039;&#039; de John Reppy, Claudio V. Russo et Yingqi Xiao présentant le langage fonctionnel concurrent CML (pour Concurrent ML), c&#039;est-à-dire que le programme peut utiliser plusieurs processus simultanément, réduisant le temps d&#039;exécution.&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia FR)]&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Leftist_tree Tas gaucher (Wikipédia EN)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.39.5334&amp;amp;rep=rep1&amp;amp;type=pdf#page=7 Manufacturing Datatypes]&lt;br /&gt;
&lt;br /&gt;
* [https://www.researchgate.net/publication/221282345_Scrap_Your_Boilerplate_A_Practical_Design_Pattern_for_Generic_Programming Scrap your boilerplate: a practical design pattern for generic programming]&lt;br /&gt;
&lt;br /&gt;
* [https://archive.alvb.in/msc/03_infoafp/papers/2012-12-18_WerkCollege_FingerTreesRalfHinze.pdf Finger trees: a simple general-purpose data structure]&lt;br /&gt;
&lt;br /&gt;
* [https://www.microsoft.com/en-us/research/wp-content/uploads/2009/09/Parallel-Concurrent-ML.pdf Parallel Concurrent ML]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12547</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12547"/>
		<updated>2020-05-28T14:41:44Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Évolution du domaine de recherche */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|150px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Code de la structure tas gaucher&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Comme il a été démontré précédemment que la longueur de la colonne vertébrale est au plus logarithmique, on peut en déduire que merge a un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur. Et puisque le temps d&#039;exécution de merge est d&#039;ordre O(log n), il en va de même pour insert.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants. De plus, merge ayant un temps d&#039;exécution d&#039;ordre O(log n), celui de deleteMin est le même.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
[[File:Schema_3.png|500px|thumb|right|Représentation des arbres binaires de rangs 0 à 3]]&lt;br /&gt;
&lt;br /&gt;
Les tas binomiaux sont des tas composés d&#039;un type plus primitif appelé arbre binomial. Les arbres binomiaux sont défini inductivement comme suit :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang 0 est un singleton de nœud ;&lt;br /&gt;
* Un arbre binomial de rang r est formé en liant deux arbres binomiaux de rang r-1 tel qu&#039;un des arbres devient l&#039;enfant le plus à gauche de l&#039;autre.&lt;br /&gt;
&lt;br /&gt;
De cette définition, on comprend qu&#039;un arbre binomial de rang r contient exactement 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; nœuds.&lt;br /&gt;
&lt;br /&gt;
Il existe une deuxième définition équivalente des arbres binomiaux :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang r est un nœud ayant r enfants t&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; à t&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt; où chaque enfant t&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; est un arbre binomial de rang r - i.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, on représente un nœud dans un arbre binomial comme un élément avec une liste de ses enfants. Par commodité, on note aussi le rang de chaque nœud.&lt;br /&gt;
&lt;br /&gt;
De plus, un tas binomial étant une liste d&#039;arbres binomiaux ne pouvant comporter qu&#039;un seul arbre d&#039;un rang donné et un arbre binomial de rang r comportant 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; éléments, on constate alors que les arbres d&#039;un tas binomial de taille n correspondent à la représentation binaire de n.&lt;br /&gt;
&lt;br /&gt;
Par exemple, la représentation binaire de 13 est 1101, donc un tas binomial de taille 13 contient un arbre de rang 0, un de rang 2 et un de rang 3 (de taille respective 1, 4 et 8).&lt;br /&gt;
De plus, comme la représentation binaire de n contient au plus ⌊log(n + 1)⌋ bits à 1, un tas binomial de taille n contient au plus ⌊log(n + 1)⌋ arbres.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BinomialHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = Node of (int * elem * tree list)&lt;br /&gt;
  type heap = tree list&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let rank (Node(r, _, _)) = r&lt;br /&gt;
  let root (Node(_, x, _)) = x&lt;br /&gt;
&lt;br /&gt;
  let link = ...&lt;br /&gt;
&lt;br /&gt;
  let rec insTree...&lt;br /&gt;
&lt;br /&gt;
  let insert = ...&lt;br /&gt;
&lt;br /&gt;
  let rec merge...&lt;br /&gt;
&lt;br /&gt;
  let rec removeMinTree...&lt;br /&gt;
&lt;br /&gt;
  let findMin = ...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin = ...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Les fonctions que nous allons étudier ici sont link, insert, merge, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;link - Lie deux arbres de même rang pour en construire un de rang supérieur&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les arbres étant triés en respectant la propriété du tas, il suffit de comparer les racines pour voir lequel reste au sommet en les liant, l&#039;autre étant ajouté à la liste des enfants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let link = fun (Node(r, x1, c1) as t1) (Node(_, x2, c2) as t2) -&amp;gt;&lt;br /&gt;
  if (O.leq x1 x2)&lt;br /&gt;
  then Node(r + 1, x1, t2 :: c1)&lt;br /&gt;
  else Node(r + 1, x2, t1 :: c2)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insertion d&#039;un élément&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les fonctions insert et merge sont toutes deux vaguement comparables à l&#039;addition de deux nombres binaires. Pour insérer un nouvel élément, on crée un singleton d&#039;arbre (donc de rang 0) puis on parcourt tous les arbres du tas par ordre croissant de rang jusqu&#039;à trouver un rang manquant en liant les arbres de rang égal en chemin. On peut comparer le lien à une retenue arithmétique.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insTree = fun t ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; [t]&lt;br /&gt;
  | t&#039; :: ts&#039; as ts -&amp;gt; if rank t &amp;lt; rank t&#039;&lt;br /&gt;
                       then t :: ts&lt;br /&gt;
                       else insTree (link t t&#039;) ts&#039;&lt;br /&gt;
&lt;br /&gt;
let insert = fun x ts -&amp;gt; insTree (Node(0, x, [])) ts&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le pire scénario étant dans un tas de taille n = 2&amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; - 1 qui nécessiterait k liens, donnant un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusion de deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, merge parcourt la liste d&#039;arbres de chaque tas par ordre croissant de rang en liant les arbres de rang égal.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec merge = fun ts1 ts2 -&amp;gt;&lt;br /&gt;
  match ts1, ts2 with&lt;br /&gt;
  | _, [] -&amp;gt; ts1&lt;br /&gt;
  | [], _ -&amp;gt; ts2&lt;br /&gt;
  | t1 :: ts1&#039;, t2 :: ts2&#039; -&amp;gt; if rank t1 &amp;lt; rank t2&lt;br /&gt;
                              then t1 :: (merge ts1&#039; ts2)&lt;br /&gt;
                              else if rank t2 &amp;lt; rank t1&lt;br /&gt;
                                   then t2 :: (merge ts1 ts2&#039;)&lt;br /&gt;
                                   else insTree (link t1 t2) (merge ts1&#039; ts2&#039;)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin et deleteMin - Trouve et supprime le minimum respectivement&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ces deux fonctions ont besoin de la même fonction auxiliaire, à savoir removeMinTree, qui trouve l&#039;arbre avec la plus petite valeur et l&#039;extrait de la liste, retournant à la fois l&#039;arbre et le reste de la liste.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec removeMinTree = fun ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | [t] -&amp;gt; (t, [])&lt;br /&gt;
  | t :: ts -&amp;gt; let (t&#039;, ts&#039;) = removeMinTree ts in&lt;br /&gt;
               if (O.leq (root t) (root t&#039;))&lt;br /&gt;
               then (t, ts)&lt;br /&gt;
               else (t&#039;, t :: ts&#039;)&lt;br /&gt;
&lt;br /&gt;
let findMin = fun ts -&amp;gt; let (t, _) = removeMinTree ts in root t&lt;br /&gt;
&lt;br /&gt;
let deleteMin = fun ts -&amp;gt; let (Node(_, x, ts1), ts2) = removeMinTree ts in merge (rev ts1) ts2&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
À partir de là, findMin retourne juste la racine de l&#039;arbre. Cependant, deleteMin est plus complexe. En effet, après avoir retiré la racine de l&#039;arbre, il faut remettre la liste d&#039;enfants de l&#039;arbre avec le reste de la liste des arbres. Cela dit, bien que chaque liste d&#039;enfants est une collection d&#039;arbres binomiaux de rang unique, ceux-ci sont triés &#039;&#039;par ordre décroissant de rang et non par ordre croissant&#039;&#039;. Afin de convertir cette liste en tas binomial valide, on l&#039;inverse à l&#039;aide de la fonction rev avant de la fusionner avec les arbres restants.&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
Après s&#039;être penché en détail sur la thèse d&#039;Okasaki, voici quelques articles plus récents en rapport avec le sujet permettant d&#039;élargir la question.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Manufacturing Datatypes&#039;&#039;&#039; par Ralf Hinze, publié en 1999 et édité par Okasaki traitant d&#039;une structure générale pour construire des structures de données purement fonctionnelles permettant de satisfaire des contraintes de taille ou de forme, notamment pour l&#039;implémentation de matrices et de certains types d&#039;arbres.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Scrap your boilerplate: a practical design pattern for generic programming&#039;&#039;&#039; de Ralf Lämmel et Simon Peyton-Jones proposant un modèle de métastructure, c&#039;est-à-dire (une structure permettant de générer du code ?), afin de remédier à l&#039;usage de code passe-partout, autrement dit du code répété à plusieurs reprises avec très peu de modifications.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Finger trees: a simple general-purpose data structure&#039;&#039;&#039; de Ralf Hinze et Ross Paterson introduisant une nouvelle structure : les arbres pointés 2-3 permettant d&#039;accéder aux feuilles dans un laps de temps amorti constant. Cette structure peut servir de base pour plusieurs autres structures de données.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Parallel Concurrent ML&#039;&#039;&#039; de John Reppy, Claudio V. Russo et Yingqi Xiao présentant le langage fonctionnel concurrent CML (pour Concurrent ML), c&#039;est-à-dire que le programme peut utiliser plusieurs processus simultanément, réduisant le temps d&#039;exécution.&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia FR)]&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Leftist_tree Tas gaucher (Wikipédia EN)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12546</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12546"/>
		<updated>2020-05-28T12:25:21Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Tas binomial */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|150px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Code de la structure tas gaucher&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Comme il a été démontré précédemment que la longueur de la colonne vertébrale est au plus logarithmique, on peut en déduire que merge a un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur. Et puisque le temps d&#039;exécution de merge est d&#039;ordre O(log n), il en va de même pour insert.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants. De plus, merge ayant un temps d&#039;exécution d&#039;ordre O(log n), celui de deleteMin est le même.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
[[File:Schema_3.png|500px|thumb|right|Représentation des arbres binaires de rangs 0 à 3]]&lt;br /&gt;
&lt;br /&gt;
Les tas binomiaux sont des tas composés d&#039;un type plus primitif appelé arbre binomial. Les arbres binomiaux sont défini inductivement comme suit :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang 0 est un singleton de nœud ;&lt;br /&gt;
* Un arbre binomial de rang r est formé en liant deux arbres binomiaux de rang r-1 tel qu&#039;un des arbres devient l&#039;enfant le plus à gauche de l&#039;autre.&lt;br /&gt;
&lt;br /&gt;
De cette définition, on comprend qu&#039;un arbre binomial de rang r contient exactement 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; nœuds.&lt;br /&gt;
&lt;br /&gt;
Il existe une deuxième définition équivalente des arbres binomiaux :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang r est un nœud ayant r enfants t&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; à t&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt; où chaque enfant t&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; est un arbre binomial de rang r - i.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, on représente un nœud dans un arbre binomial comme un élément avec une liste de ses enfants. Par commodité, on note aussi le rang de chaque nœud.&lt;br /&gt;
&lt;br /&gt;
De plus, un tas binomial étant une liste d&#039;arbres binomiaux ne pouvant comporter qu&#039;un seul arbre d&#039;un rang donné et un arbre binomial de rang r comportant 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; éléments, on constate alors que les arbres d&#039;un tas binomial de taille n correspondent à la représentation binaire de n.&lt;br /&gt;
&lt;br /&gt;
Par exemple, la représentation binaire de 13 est 1101, donc un tas binomial de taille 13 contient un arbre de rang 0, un de rang 2 et un de rang 3 (de taille respective 1, 4 et 8).&lt;br /&gt;
De plus, comme la représentation binaire de n contient au plus ⌊log(n + 1)⌋ bits à 1, un tas binomial de taille n contient au plus ⌊log(n + 1)⌋ arbres.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BinomialHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = Node of (int * elem * tree list)&lt;br /&gt;
  type heap = tree list&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let rank (Node(r, _, _)) = r&lt;br /&gt;
  let root (Node(_, x, _)) = x&lt;br /&gt;
&lt;br /&gt;
  let link = ...&lt;br /&gt;
&lt;br /&gt;
  let rec insTree...&lt;br /&gt;
&lt;br /&gt;
  let insert = ...&lt;br /&gt;
&lt;br /&gt;
  let rec merge...&lt;br /&gt;
&lt;br /&gt;
  let rec removeMinTree...&lt;br /&gt;
&lt;br /&gt;
  let findMin = ...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin = ...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Les fonctions que nous allons étudier ici sont link, insert, merge, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;link - Lie deux arbres de même rang pour en construire un de rang supérieur&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les arbres étant triés en respectant la propriété du tas, il suffit de comparer les racines pour voir lequel reste au sommet en les liant, l&#039;autre étant ajouté à la liste des enfants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let link = fun (Node(r, x1, c1) as t1) (Node(_, x2, c2) as t2) -&amp;gt;&lt;br /&gt;
  if (O.leq x1 x2)&lt;br /&gt;
  then Node(r + 1, x1, t2 :: c1)&lt;br /&gt;
  else Node(r + 1, x2, t1 :: c2)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insertion d&#039;un élément&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les fonctions insert et merge sont toutes deux vaguement comparables à l&#039;addition de deux nombres binaires. Pour insérer un nouvel élément, on crée un singleton d&#039;arbre (donc de rang 0) puis on parcourt tous les arbres du tas par ordre croissant de rang jusqu&#039;à trouver un rang manquant en liant les arbres de rang égal en chemin. On peut comparer le lien à une retenue arithmétique.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insTree = fun t ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; [t]&lt;br /&gt;
  | t&#039; :: ts&#039; as ts -&amp;gt; if rank t &amp;lt; rank t&#039;&lt;br /&gt;
                       then t :: ts&lt;br /&gt;
                       else insTree (link t t&#039;) ts&#039;&lt;br /&gt;
&lt;br /&gt;
let insert = fun x ts -&amp;gt; insTree (Node(0, x, [])) ts&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le pire scénario étant dans un tas de taille n = 2&amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; - 1 qui nécessiterait k liens, donnant un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusion de deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, merge parcourt la liste d&#039;arbres de chaque tas par ordre croissant de rang en liant les arbres de rang égal.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec merge = fun ts1 ts2 -&amp;gt;&lt;br /&gt;
  match ts1, ts2 with&lt;br /&gt;
  | _, [] -&amp;gt; ts1&lt;br /&gt;
  | [], _ -&amp;gt; ts2&lt;br /&gt;
  | t1 :: ts1&#039;, t2 :: ts2&#039; -&amp;gt; if rank t1 &amp;lt; rank t2&lt;br /&gt;
                              then t1 :: (merge ts1&#039; ts2)&lt;br /&gt;
                              else if rank t2 &amp;lt; rank t1&lt;br /&gt;
                                   then t2 :: (merge ts1 ts2&#039;)&lt;br /&gt;
                                   else insTree (link t1 t2) (merge ts1&#039; ts2&#039;)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin et deleteMin - Trouve et supprime le minimum respectivement&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ces deux fonctions ont besoin de la même fonction auxiliaire, à savoir removeMinTree, qui trouve l&#039;arbre avec la plus petite valeur et l&#039;extrait de la liste, retournant à la fois l&#039;arbre et le reste de la liste.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec removeMinTree = fun ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | [t] -&amp;gt; (t, [])&lt;br /&gt;
  | t :: ts -&amp;gt; let (t&#039;, ts&#039;) = removeMinTree ts in&lt;br /&gt;
               if (O.leq (root t) (root t&#039;))&lt;br /&gt;
               then (t, ts)&lt;br /&gt;
               else (t&#039;, t :: ts&#039;)&lt;br /&gt;
&lt;br /&gt;
let findMin = fun ts -&amp;gt; let (t, _) = removeMinTree ts in root t&lt;br /&gt;
&lt;br /&gt;
let deleteMin = fun ts -&amp;gt; let (Node(_, x, ts1), ts2) = removeMinTree ts in merge (rev ts1) ts2&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
À partir de là, findMin retourne juste la racine de l&#039;arbre. Cependant, deleteMin est plus complexe. En effet, après avoir retiré la racine de l&#039;arbre, il faut remettre la liste d&#039;enfants de l&#039;arbre avec le reste de la liste des arbres. Cela dit, bien que chaque liste d&#039;enfants est une collection d&#039;arbres binomiaux de rang unique, ceux-ci sont triés &#039;&#039;par ordre décroissant de rang et non par ordre croissant&#039;&#039;. Afin de convertir cette liste en tas binomial valide, on l&#039;inverse à l&#039;aide de la fonction rev avant de la fusionner avec les arbres restants.&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia FR)]&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Leftist_tree Tas gaucher (Wikipédia EN)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12545</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12545"/>
		<updated>2020-05-28T12:22:57Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Tas binomial */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|150px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Code de la structure tas gaucher&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Comme il a été démontré précédemment que la longueur de la colonne vertébrale est au plus logarithmique, on peut en déduire que merge a un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur. Et puisque le temps d&#039;exécution de merge est d&#039;ordre O(log n), il en va de même pour insert.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants. De plus, merge ayant un temps d&#039;exécution d&#039;ordre O(log n), celui de deleteMin est le même.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
[[File:Schema_3.png|300px|thumb|right|Représentation des arbres binaires de rangs 0 à 3]]&lt;br /&gt;
&lt;br /&gt;
Les tas binomiaux sont des tas composés d&#039;un type plus primitif appelé arbre binomial. Les arbres binomiaux sont défini inductivement comme suit :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang 0 est un singleton de nœud ;&lt;br /&gt;
* Un arbre binomial de rang r est formé en liant deux arbres binomiaux de rang r-1 tel qu&#039;un des arbres devient l&#039;enfant le plus à gauche de l&#039;autre.&lt;br /&gt;
&lt;br /&gt;
De cette définition, on comprend qu&#039;un arbre binomial de rang r contient exactement 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; nœuds.&lt;br /&gt;
&lt;br /&gt;
Il existe une deuxième définition équivalente des arbres binomiaux :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang r est un nœud ayant r enfants t&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; à t&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt; où chaque enfant t&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; est un arbre binomial de rang r - i.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, on représente un nœud dans un arbre binomial comme un élément avec une liste de ses enfants. Par commodité, on note aussi le rang de chaque nœud.&lt;br /&gt;
&lt;br /&gt;
De plus, un tas binomial étant une liste d&#039;arbres binomiaux ne pouvant comporter qu&#039;un seul arbre d&#039;un rang donné et un arbre binomial de rang r comportant 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; éléments, on constate alors que les arbres d&#039;un tas binomial de taille n correspondent à la représentation binaire de n.&lt;br /&gt;
&lt;br /&gt;
Par exemple, la représentation binaire de 13 est 1101, donc un tas binomial de taille 13 contient un arbre de rang 0, un de rang 2 et un de rang 3 (de taille respective 1, 4 et 8).&lt;br /&gt;
De plus, comme la représentation binaire de n contient au plus ⌊log(n + 1)⌋ bits à 1, un tas binomial de taille n contient au plus ⌊log(n + 1)⌋ arbres.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BinomialHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = Node of (int * elem * tree list)&lt;br /&gt;
  type heap = tree list&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let rank (Node(r, _, _)) = r&lt;br /&gt;
  let root (Node(_, x, _)) = x&lt;br /&gt;
&lt;br /&gt;
  let link = ...&lt;br /&gt;
&lt;br /&gt;
  let rec insTree...&lt;br /&gt;
&lt;br /&gt;
  let insert = ...&lt;br /&gt;
&lt;br /&gt;
  let rec merge...&lt;br /&gt;
&lt;br /&gt;
  let rec removeMinTree...&lt;br /&gt;
&lt;br /&gt;
  let findMin = ...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin = ...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Les fonctions que nous allons étudier ici sont link, insert, merge, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;link - Lie deux arbres de même rang pour en construire un de rang supérieur&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les arbres étant triés en respectant la propriété du tas, il suffit de comparer les racines pour voir lequel reste au sommet en les liant, l&#039;autre étant ajouté à la liste des enfants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let link = fun (Node(r, x1, c1) as t1) (Node(_, x2, c2) as t2) -&amp;gt;&lt;br /&gt;
  if (O.leq x1 x2)&lt;br /&gt;
  then Node(r + 1, x1, t2 :: c1)&lt;br /&gt;
  else Node(r + 1, x2, t1 :: c2)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insertion d&#039;un élément&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Les fonctions insert et merge sont toutes deux vaguement comparables à l&#039;addition de deux nombres binaires. Pour insérer un nouvel élément, on crée un singleton d&#039;arbre (donc de rang 0) puis on parcourt tous les arbres du tas par ordre croissant de rang jusqu&#039;à trouver un rang manquant en liant les arbres de rang égal en chemin. On peut comparer le lien à une retenue arithmétique.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insTree = fun t ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; [t]&lt;br /&gt;
  | t&#039; :: ts&#039; as ts -&amp;gt; if rank t &amp;lt; rank t&#039;&lt;br /&gt;
                       then t :: ts&lt;br /&gt;
                       else insTree (link t t&#039;) ts&#039;&lt;br /&gt;
&lt;br /&gt;
let insert = fun x ts -&amp;gt; insTree (Node(0, x, [])) ts&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le pire scénario étant dans un tas de taille n = 2&amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt; - 1 qui nécessiterait k liens, donnant un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusion de deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, merge parcourt la liste d&#039;arbres de chaque tas par ordre croissant de rang en liant les arbres de rang égal.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec merge = fun ts1 ts2 -&amp;gt;&lt;br /&gt;
  match ts1, ts2 with&lt;br /&gt;
  | _, [] -&amp;gt; ts1&lt;br /&gt;
  | [], _ -&amp;gt; ts2&lt;br /&gt;
  | t1 :: ts1&#039;, t2 :: ts2&#039; -&amp;gt; if rank t1 &amp;lt; rank t2&lt;br /&gt;
                              then t1 :: (merge ts1&#039; ts2)&lt;br /&gt;
                              else if rank t2 &amp;lt; rank t1&lt;br /&gt;
                                   then t2 :: (merge ts1 ts2&#039;)&lt;br /&gt;
                                   else insTree (link t1 t2) (merge ts1&#039; ts2&#039;)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin et deleteMin - Trouve et supprime le minimum respectivement&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ces deux fonctions ont besoin de la même fonction auxiliaire, à savoir removeMinTree, qui trouve l&#039;arbre avec la plus petite valeur et l&#039;extrait de la liste, retournant à la fois l&#039;arbre et le reste de la liste.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec removeMinTree = fun ls -&amp;gt;&lt;br /&gt;
  match ls with&lt;br /&gt;
  | [] -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | [t] -&amp;gt; (t, [])&lt;br /&gt;
  | t :: ts -&amp;gt; let (t&#039;, ts&#039;) = removeMinTree ts in&lt;br /&gt;
               if (O.leq (root t) (root t&#039;))&lt;br /&gt;
               then (t, ts)&lt;br /&gt;
               else (t&#039;, t :: ts&#039;)&lt;br /&gt;
&lt;br /&gt;
let findMin = fun ts -&amp;gt; let (t, _) = removeMinTree ts in root t&lt;br /&gt;
&lt;br /&gt;
let deleteMin = fun ts -&amp;gt; let (Node(_, x, ts1), ts2) = removeMinTree ts in merge (rev ts1) ts2&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
À partir de là, findMin retourne juste la racine de l&#039;arbre. Cependant, deleteMin est plus complexe. En effet, après avoir retiré la racine de l&#039;arbre, il faut remettre la liste d&#039;enfants de l&#039;arbre avec le reste de la liste des arbres. Cela dit, bien que chaque liste d&#039;enfants est une collection d&#039;arbres binomiaux de rang unique, ceux-ci sont triés &#039;&#039;par ordre décroissant de rang et non par ordre croissant&#039;&#039;. Afin de convertir cette liste en tas binomial valide, on l&#039;inverse à l&#039;aide de la fonction rev avant de la fusionner avec les arbres restants.&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia FR)]&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Leftist_tree Tas gaucher (Wikipédia EN)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12544</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12544"/>
		<updated>2020-05-28T11:25:49Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Tas binomial */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|150px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Code de la structure tas gaucher&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Comme il a été démontré précédemment que la longueur de la colonne vertébrale est au plus logarithmique, on peut en déduire que merge a un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur. Et puisque le temps d&#039;exécution de merge est d&#039;ordre O(log n), il en va de même pour insert.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants. De plus, merge ayant un temps d&#039;exécution d&#039;ordre O(log n), celui de deleteMin est le même.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
[[File:Schema_3.png|300px|thumb|right|Représentation des arbres binaires de rangs 0 à 3]]&lt;br /&gt;
&lt;br /&gt;
Les tas binomiaux sont des tas composés d&#039;un type plus primitif appelé arbre binomial. Les arbres binomiaux sont défini inductivement comme suit :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang 0 est un singleton de nœud ;&lt;br /&gt;
* Un arbre binomial de rang r est formé en liant deux arbres binomiaux de rang r-1 tel qu&#039;un des arbres devient l&#039;enfant le plus à gauche de l&#039;autre.&lt;br /&gt;
&lt;br /&gt;
De cette définition, on comprend qu&#039;un arbre binomial de rang r contient exactement 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; nœuds.&lt;br /&gt;
&lt;br /&gt;
Il existe une deuxième définition équivalente des arbres binomiaux :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang r est un nœud ayant r enfants t&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; à t&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt; où chaque enfant t&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; est un arbre binomial de rang r - i.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, on représente un nœud dans un arbre binomial comme un élément avec une liste de ses enfants. Par commodité, on note aussi le rang de chaque nœud.&lt;br /&gt;
&lt;br /&gt;
De plus, un tas binomial étant une liste d&#039;arbres binomiaux ne pouvant comporter qu&#039;un seul arbre d&#039;un rang donné et un arbre binomial de rang r comportant 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; éléments, on constate alors que les arbres d&#039;un tas binomial de taille n correspondent à la représentation binaire de n.&lt;br /&gt;
&lt;br /&gt;
Par exemple, la représentation binaire de 13 est 1101, donc un tas binomial de taille 13 contient un arbre de rang 0, un de rang 2 et un de rang 3 (de taille respective 1, 4 et 8).&lt;br /&gt;
De plus, comme la représentation binaire de n contient au plus ⌊log(n + 1)⌋ bits à 1, un tas binomial de taille n contient au plus ⌊log(n + 1)⌋ arbres.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BinomialHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = Node of (int * elem * tree list)&lt;br /&gt;
  type heap = tree list&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let rank (Node(r, _, _)) = r&lt;br /&gt;
  let root (Node(_, x, _)) = x&lt;br /&gt;
&lt;br /&gt;
  let link = fun (Node(r, x1, c1) as t1) (Node(_, x2, c2) as t2) -&amp;gt;&lt;br /&gt;
    if (O.leq x1 x2)&lt;br /&gt;
    then Node(r + 1, x1, t2 :: c1)&lt;br /&gt;
    else Node(r + 1, x2, t1 :: c2)&lt;br /&gt;
&lt;br /&gt;
  let rec insTree...&lt;br /&gt;
&lt;br /&gt;
  let insert = fun x ts -&amp;gt; insTree (Node(0, x, [])) ts&lt;br /&gt;
&lt;br /&gt;
  let rec merge...&lt;br /&gt;
&lt;br /&gt;
  let rec removeMinTree...&lt;br /&gt;
&lt;br /&gt;
  let findMin = fun ts -&amp;gt; let (t, _) = removeMinTree ts in root t&lt;br /&gt;
&lt;br /&gt;
  let deleteMin = fun ts -&amp;gt; let (Node(_, x, ts1), ts2) = removeMinTree ts in merge (rev ts1) ts2&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Les fonctions que nous allons étudier ici sont insTree, merge et removeMinTree.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insTree -&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia FR)]&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Leftist_tree Tas gaucher (Wikipédia EN)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12543</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12543"/>
		<updated>2020-05-28T09:10:18Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Tas binomial */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|150px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Code de la structure tas gaucher&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Comme il a été démontré précédemment que la longueur de la colonne vertébrale est au plus logarithmique, on peut en déduire que merge a un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur. Et puisque le temps d&#039;exécution de merge est d&#039;ordre O(log n), il en va de même pour insert.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants. De plus, merge ayant un temps d&#039;exécution d&#039;ordre O(log n), celui de deleteMin est le même.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
[[File:Schema_3.png|300px|thumb|right|Représentation des arbres binaires de rangs 0 à 3]]&lt;br /&gt;
&lt;br /&gt;
Les tas binomiaux sont des tas composés d&#039;un type plus primitif appelé arbre binomial. Les arbres binomiaux sont défini inductivement comme suit :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang 0 est un singleton de nœud ;&lt;br /&gt;
* Un arbre binomial de rang r est formé en liant deux arbres binomiaux de rang r-1 tel qu&#039;un des arbres devient l&#039;enfant le plus à gauche de l&#039;autre.&lt;br /&gt;
&lt;br /&gt;
De cette définition, on comprend qu&#039;un arbre binomial de rang r contient exactement 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; nœuds.&lt;br /&gt;
&lt;br /&gt;
Il existe une deuxième définition équivalente des arbres binomiaux :&lt;br /&gt;
&lt;br /&gt;
* Un arbre binomial de rang r est un nœud ayant r enfants t&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; à t&amp;lt;sub&amp;gt;r&amp;lt;/sub&amp;gt; où chaque enfant t&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; est un arbre binomial de rang r - i.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, on représente un nœud dans un arbre binomial comme un élément avec une liste de ses enfants. Par commodité, on note aussi le rang de chaque nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BinomialHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = Node of (int * elem * tree list)&lt;br /&gt;
  type heap = tree list&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let rank (Node(r, _, _)) = r&lt;br /&gt;
  let root (Node(_, x, _)) = x&lt;br /&gt;
&lt;br /&gt;
  let link = fun (Node(r, x1, c1) as t1) (Node(_, x2, c2) as t2) -&amp;gt;&lt;br /&gt;
    if (O.leq x1 x2)&lt;br /&gt;
    then Node(r + 1, x1, t2 :: c1)&lt;br /&gt;
    else Node(r + 1, x2, t1 :: c2)&lt;br /&gt;
&lt;br /&gt;
  let rec insTree...&lt;br /&gt;
&lt;br /&gt;
  let insert = fun x ts -&amp;gt; insTree (Node(0, x, [])) ts&lt;br /&gt;
&lt;br /&gt;
  let rec merge...&lt;br /&gt;
&lt;br /&gt;
  let rec removeMinTree...&lt;br /&gt;
&lt;br /&gt;
  let findMin = fun ts -&amp;gt; let (t, _) = removeMinTree ts in root t&lt;br /&gt;
&lt;br /&gt;
  let deleteMin = fun ts -&amp;gt; let (Node(_, x, ts1), ts2) = removeMinTree ts in merge (rev ts1) ts2&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia FR)]&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Leftist_tree Tas gaucher (Wikipédia EN)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_3.png&amp;diff=12542</id>
		<title>Fichier:Schema 3.png</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_3.png&amp;diff=12542"/>
		<updated>2020-05-28T07:54:54Z</updated>

		<summary type="html">&lt;p&gt;Dornel : Représentation des arbres binomiaux de rang 0 à 3&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Représentation des arbres binomiaux de rang 0 à 3&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12541</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12541"/>
		<updated>2020-05-28T07:41:48Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Sources et annexes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|150px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Code de la structure tas gaucher&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Comme il a été démontré précédemment que la longueur de la colonne vertébrale est au plus logarithmique, on peut en déduire que merge a un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur. Et puisque le temps d&#039;exécution de merge est d&#039;ordre O(log n), il en va de même pour insert.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants. De plus, merge ayant un temps d&#039;exécution d&#039;ordre O(log n), celui de deleteMin est le même.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia FR)]&lt;br /&gt;
&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Leftist_tree Tas gaucher (Wikipédia EN)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12540</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12540"/>
		<updated>2020-05-28T07:40:53Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Tas gaucher */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|150px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Code de la structure tas gaucher&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Comme il a été démontré précédemment que la longueur de la colonne vertébrale est au plus logarithmique, on peut en déduire que merge a un temps d&#039;exécution d&#039;ordre O(log n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur. Et puisque le temps d&#039;exécution de merge est d&#039;ordre O(log n), il en va de même pour insert.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants. De plus, merge ayant un temps d&#039;exécution d&#039;ordre O(log n), celui de deleteMin est le même.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12539</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12539"/>
		<updated>2020-05-28T07:37:35Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Tas gaucher */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|300px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Soit r le rang de la racine d&#039;un tas gaucher de taille n. On peut en déduire que chaque nœud de profondeur inférieure ou égale à r-1 a exactement deux enfants, car sinon r aurait une valeur plus faible ou la propriété gauchère ne serait pas respectée. Ainsi, on peut affirmer que la taille d&#039;un tas est donc d&#039;au moins 2&amp;lt;sup&amp;gt;r&amp;lt;/sup&amp;gt; - 1. De ce fait, on peut en déduire que le rang de la racine vaut au plus log(n + 1). Par conséquent, la colonne vertébrale droite d&#039;un tas gaucher de taille n comporte au plus ⌊log(n + 1)⌋ éléments.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12538</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12538"/>
		<updated>2020-05-27T18:40:54Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Tas gaucher */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|300px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Un arbre complet de profondeur n comporte 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; branches. Ainsi, &amp;lt;WIP&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12537</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12537"/>
		<updated>2020-05-27T17:21:33Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Tas gaucher */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|300px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
Un arbre complet de profondeur n comporte 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; branches. Ainsi, &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12536</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12536"/>
		<updated>2020-05-27T16:40:00Z</updated>

		<summary type="html">&lt;p&gt;Dornel : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
Un tas est une version d&#039;arbre particulière. En effet, elle se caractérise par le fait que la valeur de chaque nœud ne peut être plus grand que n&#039;importe laquelle de ses enfants. Cette caractéristique fait que l&#039;élément le plus petit se trouve toujours à la racine.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_2.png|300px|thumb|right|Exemple de tas gaucher et illustration de la notion de rang.]]&lt;br /&gt;
&lt;br /&gt;
Un tas est dit gaucher si le rang de n&#039;importe quel enfant de gauche est supérieur ou égal à celui de l&#039;enfant de droit qui lui est associé. On appelle rang d&#039;un nœud la longueur de sa &#039;&#039;colonne vertébrale droite&#039;&#039;, c&#039;est-à-dire le chemin le plus à droite qui va de ce nœud à une feuille. Une conséquence simple de cette propriété est que la colonne vertébrale droite d&#039;un nœud constitue le chemin le plus court de ce nœud à une feuille.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module LeftistHeap (O: ORDERED) = struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type heap = Nil | Cons of (int * heap * elem * heap)&lt;br /&gt;
&lt;br /&gt;
  let rank = fun h -&amp;gt;&lt;br /&gt;
    match h with&lt;br /&gt;
    | Nil -&amp;gt; 0&lt;br /&gt;
    | Cons(r, _, _, _) -&amp;gt; r&lt;br /&gt;
&lt;br /&gt;
  let makeH...&lt;br /&gt;
&lt;br /&gt;
  let merge...&lt;br /&gt;
&lt;br /&gt;
  let insert...&lt;br /&gt;
&lt;br /&gt;
  exception EmptyHeap&lt;br /&gt;
&lt;br /&gt;
  let findMin...&lt;br /&gt;
&lt;br /&gt;
  let deleteMin...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons étudier les fonctions merge, insert, findMin et deleteMin.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;merge - Fusionne deux tas&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afin de simplifier l&#039;opération, on remarque que la colonne vertébrale droite d&#039;un tas gaucher est ordonnée. On en déduit que le concept clé est de fusionner les colonnes vertébrales des deux tas comme on fusionnerait deux listes triées avant d&#039;échanger les enfants le long de la branche afin de restaurer la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let makeH = fun v a b -&amp;gt;&lt;br /&gt;
  if rank a &amp;gt;= rank b&lt;br /&gt;
  then Cons(rank b + 1, a, v, b)&lt;br /&gt;
  else Cons(rank a + 1, b, v, a)&lt;br /&gt;
&lt;br /&gt;
let rec merge = fun x y -&amp;gt;&lt;br /&gt;
  match x, y with&lt;br /&gt;
  | Nil, _ -&amp;gt; y&lt;br /&gt;
  | _, Nil -&amp;gt; x&lt;br /&gt;
  | Cons(_, e11, v1, e12), Cons(_, e21, v2, e22) -&amp;gt; if (O.leq v1 v2)&lt;br /&gt;
                                                    then makeH v1 e11 (merge e12 y)&lt;br /&gt;
                                                    else makeH v2 e21 (merge x e22)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction intermédiaire : makeH.&lt;br /&gt;
&lt;br /&gt;
Cette fonction compare le rang des deux tas et échange les enfants si besoin afin de respecter la propriété gauchère.&lt;br /&gt;
&lt;br /&gt;
Ensuite, concernant merge en elle-même, on compare les valeurs des deux racines et on fait appel à makeH pour combiner et potentiellement échanger les enfants.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Une fois merge défini, il suffit d&#039;établir que l&#039;on veut fusionner le tas avec un tas d&#039;une seule valeur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let insert = fun v h -&amp;gt; (merge (Cons(1, Nil, v, Nil)) h)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;findMin - Récupère le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Comme le minimum est par définition à la racine, le temps d&#039;exécution de findMin est d&#039;ordre O(1), donc constant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let findMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, _, v, _) -&amp;gt; v&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;deleteMin - Supprime le minimum&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Similairement, le minimum étant à la racine, on peut établir que le supprimer revient à fusionner ses enfants.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let deleteMin = fun h -&amp;gt;&lt;br /&gt;
  match h with&lt;br /&gt;
  | Nil -&amp;gt; raise EmptyHeap&lt;br /&gt;
  | Cons(_, a, _, b) -&amp;gt; merge a b&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_2.png&amp;diff=12535</id>
		<title>Fichier:Schema 2.png</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_2.png&amp;diff=12535"/>
		<updated>2020-05-27T15:40:10Z</updated>

		<summary type="html">&lt;p&gt;Dornel : Exemple de tas gaucher et illustration de la notion de rang&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Exemple de tas gaucher et illustration de la notion de rang&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12534</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12534"/>
		<updated>2020-05-27T15:16:59Z</updated>

		<summary type="html">&lt;p&gt;Dornel : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
Exemple : Le premier bloc de code de liste chaînée (voir ci-dessous) est une abstraction de cette structure, avec le type élément et les divers constructeurs, destructeurs et méthodes sur cette structure.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
Exemple : Posons qu&#039;un élément est soit null, soit le couplet liant un entier et un pointeur vers l&#039;élément suivant. Ceci correspond à l&#039;implémentation de l&#039;abstraction de la structure liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
Exemple : Soit une liste chaînée d&#039;entiers. Il s&#039;agit d&#039;une version d&#039;une liste chaînée.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type est défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
L&#039;idée est de &amp;quot;déconstruire&amp;quot; xs afin de se retrouver avec une liste vide. Au fur et à mesure, on reconstruit xs élément par élément avant d&#039;y accoler ys lorsque cet objectif est atteint.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme pour append, on déconstruit puis reconstruit xs, sauf que l&#039;on a un compteur qui est décrémenté de 1 par élément reconstruit. S&#039;il atteint 0, la valeur de l&#039;élément actuel est replacé par y. Si on reconstruit xs dans son intégralité (donc qu&#039;il ne reste que la liste vide) avant que le compteur ait atteint 0, on lève une exception.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on utilise le fait que la fonction tail renvoie toute la liste sauf la tête. Ainsi, on peut accoler tous les suffixes un par un, jusqu&#039;à s&#039;arrêter avec la liste vide.&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
Dans le code ci-dessous, BalancedTree est un foncteur, autrement dit une fonction qui prendre comme paramètre un module O de type ORDERED.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On regarde si la valeur du nœud est strictement plus grande que celle recherchée. Si oui, on continue à chercher à gauche. Sinon, on regarde si la valeur du nœud est strictement inférieure à celle recherchée. Si c&#039;est le cas, on poursuit la recherche à gauche. Sinon, il y a égalité et la valeur est bien membre de l&#039;arbre. Si on atteint une feuille sans avoir eu d&#039;égalité, alors la valeur n&#039;appartient pas à l&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
En effet, le pire scénario, ici la branche qui tourne toujours à droite jusqu&#039;au bout de l&#039;arbre, exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat, étant donné qu&#039;il effectue deux comparaisons par nœud.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise dans le pire des cas que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, une par niveau de profondeur et une supplémentaire pour vérifier l&#039;égalité une fois arrivé aux feuilles.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Comme précédemment, on regarde s&#039;il faut continuer à gauche ou à droite en reconstruisant une copie de l&#039;arbre au fur et à mesure. Cependant, si la valeur à ajouter est déjà présente dans l&#039;arbre, on recopie également ce nœud, ce qui fait que &#039;&#039;&#039;la branche de recherche est testée de la racine à la feuille&#039;&#039;&#039; sans aucun changement à appliquer. En levant une exception si l&#039;on trouve la valeur à ajouter dans l&#039;arbre, on optimise le temps d&#039;exécution en ne faisant pas de copie inutile.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ainsi, on s&#039;arrête si l&#039;on trouve la valeur à ajouter et l&#039;on retourne une copie de l&#039;arbre tel quel.&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== Évolution du domaine de recherche ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12533</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12533"/>
		<updated>2020-05-27T08:36:26Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Arbre de recherche binaire */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type a été défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaires.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre de gauche et inférieure à celles dans le sous-arbre de droite.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et bien que cette fonction soit valide, on peut encore l&#039;optimiser.&lt;br /&gt;
En effet, le pire scénario exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, ce qui est plus efficace que la fonction précédente.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, l&#039;optimisation possible provient du fait que &#039;&#039;&#039;la branche de recherche complète est copiée lorsque l&#039;on ajoute un élément déjà existant&#039;&#039;&#039;. En renvoyant une exception dans ce cas, on évite de copier inutilement.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== État actuel du problème ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12532</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12532"/>
		<updated>2020-05-27T08:35:28Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Liste chaînée */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type a été défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une structure de données efficace basée sur la liste chaînée peut comporter deux pointeurs globaux, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaire.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre à sa gauche et inférieure à celles dans le sous-arbre à sa droite.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et bien que cette fonction soit valide, on peut encore l&#039;optimiser.&lt;br /&gt;
En effet, le pire scénario exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, ce qui est plus efficace que la fonction précédente.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, l&#039;optimisation possible provient du fait que &#039;&#039;&#039;la branche de recherche complète est copiée lorsque l&#039;on ajoute un élément déjà existant&#039;&#039;&#039;. En renvoyant une exception dans ce cas, on évite de copier inutilement.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== État actuel du problème ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12531</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12531"/>
		<updated>2020-05-27T08:31:54Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Évaluation stricte et évaluation paresseuse */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures dites amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation plus facilement (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type a été défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une liste chaînée comporte généralement deux pointeurs, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaire.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre à sa gauche et inférieure à celles dans le sous-arbre à sa droite.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et bien que cette fonction soit valide, on peut encore l&#039;optimiser.&lt;br /&gt;
En effet, le pire scénario exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, ce qui est plus efficace que la fonction précédente.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, l&#039;optimisation possible provient du fait que &#039;&#039;&#039;la branche de recherche complète est copiée lorsque l&#039;on ajoute un élément déjà existant&#039;&#039;&#039;. En renvoyant une exception dans ce cas, on évite de copier inutilement.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== État actuel du problème ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12530</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12530"/>
		<updated>2020-05-27T07:57:10Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Arbre de recherche binaire */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type a été défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une liste chaînée comporte généralement deux pointeurs, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaire.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre à sa gauche et inférieure à celles dans le sous-arbre à sa droite.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et bien que cette fonction soit valide, on peut encore l&#039;optimiser.&lt;br /&gt;
En effet, le pire scénario exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, ce qui est plus efficace que la fonction précédente.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, l&#039;optimisation possible provient du fait que &#039;&#039;&#039;la branche de recherche complète est copiée lorsque l&#039;on ajoute un élément déjà existant&#039;&#039;&#039;. En renvoyant une exception dans ce cas, on évite de copier inutilement.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== État actuel du problème ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12529</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12529"/>
		<updated>2020-05-27T07:53:46Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Arbre de recherche binaire */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type a été défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une liste chaînée comporte généralement deux pointeurs, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaire.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre à sa gauche et inférieure à celles dans le sous-arbre à sa droite.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et bien que cette fonction soit valide, on peut encore l&#039;optimiser.&lt;br /&gt;
En effet, le pire scénario exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, ce qui est plus efficace que la fonction précédente.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, l&#039;optimisation possible provient du fait que &#039;&#039;&#039;la branche de recherche complète est copiée lorsque l&#039;on ajoute un élément déjà existant&#039;&#039;&#039;. En renvoyant une exception dans ce cas, on évite de copier inutilement.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== État actuel du problème ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12528</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12528"/>
		<updated>2020-05-27T07:50:19Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Solutions proposées */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type a été défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une liste chaînée comporte généralement deux pointeurs, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then ys&lt;br /&gt;
  else cons (head xs) (append (tail xs) ys)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  else if i = 0&lt;br /&gt;
       then cons y (tail xs)&lt;br /&gt;
       else cons (head xs) (update (tail xs) (i - 1) y)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  if is_empty xs&lt;br /&gt;
  then nil&lt;br /&gt;
  else cons xs (suffixes (tail xs))&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
Il est possible d&#039;utiliser des méthodes de recherche plus complexes lorsque l&#039;on utilise une structure où un élément pointe vers plus qu&#039;un seul autre élément. Prenons par exemple les arbres de recherche binaire.&lt;br /&gt;
&lt;br /&gt;
Un arbre de recherche binaire est un arbre dont les valeurs stockées dans chaque élément sont rangées par &#039;&#039;ordre symétrique&#039;&#039;, c&#039;est-à-dire que pour un nœud donné, sa valeur est supérieure à toutes les valeurs stockées dans le sous-arbre à sa gauche et inférieure à celles dans le sous-arbre à sa droite.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module BalancedTree(O: ORDERED)= struct&lt;br /&gt;
  type elem = O.t&lt;br /&gt;
&lt;br /&gt;
  type tree = E | T of (tree * elem * tree)&lt;br /&gt;
&lt;br /&gt;
  let rec member...&lt;br /&gt;
&lt;br /&gt;
  let rec insert...&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Penchons-nous sur les méthodes member et insert.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;member - Vérifie si une valeur est présente dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
En reprenant notre type ordonné, on remarque que l&#039;on ne dispose que de 3 fonctions de test, à savoir l&#039;égalité (O.eq), l&#039;infériorité stricte (O.lt) et l&#039;infériorité (O.leq).&lt;br /&gt;
&lt;br /&gt;
Pour construire cette fonction, on serait tenté d&#039;écrire&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else if (O.lt y x)&lt;br /&gt;
                         then member e2 x&lt;br /&gt;
                         else true&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et bien que cette fonction soit valide, on peut encore l&#039;optimiser.&lt;br /&gt;
En effet, le pire scénario exigerait &#039;&#039;&#039;2n comparaisons&#039;&#039;&#039; (avec n la profondeur de l&#039;arbre) pour retourner un résultat.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec member =&lt;br /&gt;
  let rec member_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; O.eq aux x&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then member_aux e1 aux x&lt;br /&gt;
                      else member_aux e2 y x&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; false&lt;br /&gt;
  | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                    then member e1 x&lt;br /&gt;
                    else member_aux e2 y x&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on fait appel à une fonction auxiliaire itérative stockant une valeur intermédiaire. Cette fonction fait appel au fait que si x n&#039;est pas strictement inférieur à y, alors x est supérieur &#039;&#039;ou égal&#039;&#039; à y. Ainsi, on parcourt la branche correspondante comme précédemment, mais on garde ce candidat potentiel jusqu&#039;à ce que l&#039;on atteigne une feuille. Ainsi, on ne réalise que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;, ce qui est plus efficace que la fonction précédente.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;insert - Insère une valeur dans l&#039;arbre&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Pour l&#039;insertion, on procède similairement pour atteindre la feuille correspondante.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | E -&amp;gt; T(E, x, E)&lt;br /&gt;
  | T(e1, y, e2) as s -&amp;gt; if (O.lt x y)&lt;br /&gt;
                         then T((insert e1 x), y, e2)&lt;br /&gt;
                         else if (O.lt y x)&lt;br /&gt;
                              then T(e1, y, (insert e2 x))&lt;br /&gt;
                              else s&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, l&#039;optimisation possible provient du fait que &#039;&#039;la branche de recherche complète est copiée lorsque l&#039;on ajoute un élément déjà existant&#039;&#039;. En renvoyant une exception dans ce cas, on évite de copier inutilement.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;exception Already_there&lt;br /&gt;
&lt;br /&gt;
let rec insert = fun xs x -&amp;gt;&lt;br /&gt;
  try begin&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if(O.lt x y)&lt;br /&gt;
                      then T((insert e1 x), y, e2)&lt;br /&gt;
                      else if (O.lt y x)&lt;br /&gt;
                           then T(e1, y, (insert e2 x))&lt;br /&gt;
                           else raise Already_there&lt;br /&gt;
  end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Et comme pour member, il est possible d&#039;optimiser le nombre de comparaisons, ce qui donne au final une fonction qui ressemble à ceci&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec insert =&lt;br /&gt;
  let rec insert_aux = fun xs aux x -&amp;gt;&lt;br /&gt;
    match xs with&lt;br /&gt;
    | E -&amp;gt; if (O.eq aux x)&lt;br /&gt;
           then raise Already_there&lt;br /&gt;
           else T(E, x, E)&lt;br /&gt;
    | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                      then T((insert_aux e1 aux x), y, e2)&lt;br /&gt;
                      else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
  in fun xs x -&amp;gt;&lt;br /&gt;
    try begin&lt;br /&gt;
      match xs with&lt;br /&gt;
      | E -&amp;gt; T(E, x, E)&lt;br /&gt;
      | T(e1, y, e2) -&amp;gt; if (O.lt x y)&lt;br /&gt;
                        then T((insert e1 x), y, e2)&lt;br /&gt;
                        else T(e1, y, (insert_aux e2 y x))&lt;br /&gt;
    end with Already_there -&amp;gt; xs&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On se retrouve donc avec une fonction d&#039;insertion qui &#039;&#039;&#039;ne copie pas inutilement&#039;&#039;&#039; et qui ne réalise pas plus que &#039;&#039;&#039;n + 1 comparaisons&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== État actuel du problème ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12527</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12527"/>
		<updated>2020-05-26T16:47:02Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Liste chaînée */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type a été défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;append - Concaténation de deux listes&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Soient xs et ys deux listes et zs la concaténation de xs et ys.&lt;br /&gt;
&lt;br /&gt;
En impératif, une liste chaînée comporte généralement deux pointeurs, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | [] -&amp;gt; ys&lt;br /&gt;
  | x :: xs&#039; -&amp;gt; x :: (append xs&#039; ys);;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;update - Mise à jour d&#039;un nœud&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
On cherche à changer la valeur x au rang i dans xs par la valeur y.&lt;br /&gt;
&lt;br /&gt;
En impératif, on cherche le nœud concerné et on change la valeur. Le temps d&#039;exécution est d&#039;ordre O(n) dans le pire des cas, mais le xs original est perdu.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, la méthode de recherche est la même. Le temps d&#039;exécution est toujours d&#039;ordre O(n) dans le pire des cas, mais on récupère ys, reconstruction altérée de xs tout en conservant l&#039;original.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec update = fun xs i y -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | [] -&amp;gt; raise Index_out_of_bounds (* Si la liste est vide, il n&#039;y a rien à remplacer ! *)&lt;br /&gt;
  | x :: xs&#039; -&amp;gt; if i = 0&lt;br /&gt;
                then y :: xs&#039;&lt;br /&gt;
                else x :: (update xs&#039; (i - 1) y);;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;suffixes - Afficher tous les suffixes d&#039;une liste par ordre décroissant de taille&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Par exemple, la liste [1, 2, 3, 4] doit retourner [[1, 2, 3, 4], [2, 3, 4], [3, 4], [4], []].&lt;br /&gt;
&lt;br /&gt;
Le temps d&#039;exécution de cette fonction est d&#039;ordre O(n).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec suffixes = fun xs -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | [] -&amp;gt; []&lt;br /&gt;
  | x :: xs&#039; as s -&amp;gt; s :: (suffixes xs&#039;);;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== État actuel du problème ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12526</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12526"/>
		<updated>2020-05-26T15:43:00Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Liste chaînée */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type a été défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, nous allons observer les méthodes, à savoir append, update et suffixes.&lt;br /&gt;
&lt;br /&gt;
==== append - Concaténation de deux listes - zs = xs :: ys ====&lt;br /&gt;
&lt;br /&gt;
En impératif, une liste chaînée comporte généralement deux pointeurs, un sur le premier élément et un sur le dernier. Ainsi, pour concaténer xs et ys, il suffit de modifier le dernier élément de xs pour qu&#039;il pointe vers le premier de ys. L&#039;avantage, c&#039;est que le temps d&#039;exécution est d&#039;ordre O(1), donc constant. Cependant, en obtenant zs, on garde ys mais on perd xs.&lt;br /&gt;
&lt;br /&gt;
En fonctionnel, zs est une reconstruction de xs à laquelle on accole ys. Si on note n la longueur de xs, la fonction a un temps d&#039;exécution d&#039;ordre O(n), mais on garde toujours xs et ys.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;let rec append = fun xs ys -&amp;gt;&lt;br /&gt;
  match xs with&lt;br /&gt;
  | [] -&amp;gt; ys&lt;br /&gt;
  | x :: xs&#039; -&amp;gt; x :: (append xs&#039; ys);;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== État actuel du problème ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12525</id>
		<title>Structures de données purement fonctionnelles</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Structures_de_donn%C3%A9es_purement_fonctionnelles&amp;diff=12525"/>
		<updated>2020-05-26T13:22:06Z</updated>

		<summary type="html">&lt;p&gt;Dornel : Page créée avec « == Présentation du problème ==  Lorsque l&amp;#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de tro... »&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Présentation du problème ==&lt;br /&gt;
&lt;br /&gt;
Lorsque l&#039;on souhaite coder une structure de données dans un langage impératif comme C, Ada, Pascal ou Perl, il est très facile de trouver des livres sur le sujet. En revanche, si l&#039;on souhaite utiliser le paradigme fonctionnel, que ce soit Lisp, Haskell ou OCaml, le choix est nettement plus restreint.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;q&amp;gt;Un programmeur peut choisir le paradigme de son langage, pourvu que ce soit impératif.&amp;lt;/q&amp;gt;&lt;br /&gt;
- Chris Okasaki, pastiche d&#039;une citation de Ford, &#039;&#039;Purely functional data structures&#039;&#039;, 1996&lt;br /&gt;
&lt;br /&gt;
Ainsi, Chris Okasaki a voulu explorer à travers sa thèse (et son livre la développant) diverses structures de données adaptées au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Mais avant d&#039;étudier les solutions proposées, il est nécessaire de définir quelques contraintes et termes.&lt;br /&gt;
&lt;br /&gt;
=== Problèmes liés au paradigme fonctionnel ===&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Schema_1.png|300px|thumb|right|Différence entre structure éphémère et persistante illustrée par une liste chaînée dont on supprime le dernier élément.]]&lt;br /&gt;
&lt;br /&gt;
Le paradigme fonctionnel est principalement basé sur l&#039;évaluation de fonctions et d&#039;expressions mathématiques, et tout ce qui ne peut être représenté ainsi n&#039;est pas admis.&lt;br /&gt;
&lt;br /&gt;
De ce fait naît le premier obstacle à la création de structures de données purement fonctionnelles : Le changement d&#039;état étant banni, &#039;&#039;&#039;on ne peut pas réaliser d&#039;assignation de variable&#039;&#039;&#039;.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Là où les langages impératifs font fréquemment usage de l&#039;assignation de variable et de la modification de valeurs, il faut trouver d&#039;autres solutions en fonctionnel pour contourner ce problème. Okasaki compare le lien entre l&#039;assignation et le programmeur à celui entre les couteaux et un chef cuisinier. Dans les deux cas, un mauvais usage peut être dangereux et destructeur, mais extrêmement efficace avec un usage intelligent.&lt;br /&gt;
&lt;br /&gt;
Une deuxième difficulté liée à l&#039;absence d&#039;assignation provient du fait que l&#039;on attende davantage une &#039;&#039;&#039;persistance&#039;&#039;&#039; d&#039;une structure de données fonctionnelle. En effet, là où il est admis que l&#039;actualisation d&#039;une structure impérative détruit l&#039;ancienne version pour ne garder que la nouvelle (ce genre de structure de données est dit &amp;quot;éphémère&amp;quot;), on s&#039;attend que l&#039;actualisation d&#039;une structure fonctionnelle donne l&#039;accès aux deux versions (d&#039;où la notion de structure &amp;quot;persistante&amp;quot;). Il est possible d&#039;avoir des structures persistantes en impératif, mais on associera ici la notion d&#039;éphémérité au paradigme impératif tandis que la persistance sera liée au paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
Enfin, un troisième problème lié au paradigme fonctionnel relève du temps d&#039;exécution, le fonctionnel étant généralement considéré comme étant &#039;&#039;&#039;moins efficace que l&#039;impératif&#039;&#039;&#039;. Ainsi, il est nécessaire de trouver des structures de données qui soient aussi efficaces que celles utilisées en impératif.&lt;br /&gt;
&lt;br /&gt;
=== Évaluation stricte et évaluation paresseuse ===&lt;br /&gt;
&lt;br /&gt;
On appelle &#039;&#039;&#039;évaluation stricte&#039;&#039;&#039; une technique d&#039;implémentation d&#039;un programme récursif où les arguments sont évalués avant le corps de la fonction.&lt;br /&gt;
L&#039;évaluation est dite &#039;&#039;&#039;paresseuse&#039;&#039;&#039; quand les arguments sont évalués lors du premier appel par la fonction avant d&#039;être mis en cache pour un autre usage ultérieur.&lt;br /&gt;
&lt;br /&gt;
Chaque type d&#039;évaluation a ses avantages et inconvénients. Une évaluation stricte permettra de gérer le cas &amp;quot;Pire scénario&amp;quot; tandis qu&#039;une évaluation paresseuse sera plus à l&#039;aise avec les structures amorties.&lt;br /&gt;
&lt;br /&gt;
Un avantage indéniable qu&#039;a cependant l&#039;évaluation stricte sur l&#039;évaluation paresseuse est que l&#039;on peut calculer le temps d&#039;évaluation (notion de [https://fr.wikipedia.org/wiki/Comparaison_asymptotique comparaison asymptotique], notamment du grand O de Landau).&lt;br /&gt;
&lt;br /&gt;
=== Vocabulaire ===&lt;br /&gt;
&lt;br /&gt;
Avant de commencer à étudier les solutions proposées par Okasaki, il est nécessaire de poser quelques termes de vocabulaire.&lt;br /&gt;
&lt;br /&gt;
; Abstraction&lt;br /&gt;
: Un type de données abstrait, autrement dit un type et un ensemble de fonctions agissant sur ce type.&lt;br /&gt;
&lt;br /&gt;
; Implémentation&lt;br /&gt;
: Une réalisation concrète d&#039;une abstraction. Il est important de noter qu&#039;une implémentation ne correspond pas nécessairement à du code, un modèle concret suffit.&lt;br /&gt;
&lt;br /&gt;
; Objet / Version&lt;br /&gt;
: Une instance d&#039;un type de données, telle une variante spécifique de liste ou d&#039;arbre.&lt;br /&gt;
&lt;br /&gt;
; Identité persistante&lt;br /&gt;
: Une identité unique et invariante malgré les changements. Par exemple, &amp;quot;la pile&amp;quot; en parlant de toutes ses différentes versions correspond à son identité persistante.&lt;br /&gt;
&lt;br /&gt;
Maintenant que nous avons posé des bases solides, nous pouvons commencer à étudier les diverses structures de données proposées par Okasaki. Le langage utilisé est OCaml.&lt;br /&gt;
&lt;br /&gt;
== Solutions proposées ==&lt;br /&gt;
&lt;br /&gt;
En se basant sur la présentation, on peut faire remarquer deux points :&lt;br /&gt;
* Afin d&#039;avoir des structures persistantes, il est nécessaire de travailler sur une copie de l&#039;argument plutôt que l&#039;argument lui-même&lt;br /&gt;
* À l&#039;exception de la liste chaînée, toutes les structures évoquées ci-après ne fonctionnent qu&#039;avec des types ordonnés.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type ORDERED = sig&lt;br /&gt;
  type t&lt;br /&gt;
  val eq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val lt: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
  val leq: t -&amp;gt; t -&amp;gt; bool&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Code permettant d&#039;implémenter un type polymorphe ordonné. On admet que ce type a été défini pour toutes les structures ci-dessous.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Liste chaînée ===&lt;br /&gt;
&lt;br /&gt;
Cette structure basique sert d&#039;introduction à la persistance, ce qui nous permet de présenter les différences d&#039;implémentation de cette structure dans un paradigme impératif comparé à un paradigme fonctionnel.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;OCaml&amp;quot;&amp;gt;module type LIST = sig&lt;br /&gt;
  type &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Constructeurs *)&lt;br /&gt;
  val nil: &#039;a t&lt;br /&gt;
  val cons: &#039;a -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val is_empty: &#039;a t -&amp;gt; bool&lt;br /&gt;
  &lt;br /&gt;
  (* Destructeurs *)&lt;br /&gt;
  val head: &#039;a t -&amp;gt; &#039;a&lt;br /&gt;
  val tail: &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
&lt;br /&gt;
  (* Méthodes *)&lt;br /&gt;
  val append: &#039;a t -&amp;gt; &#039;a t -&amp;gt; &#039;a t&lt;br /&gt;
  val update: &#039;a t -&amp;gt; int -&amp;gt; &#039;a -&amp;gt; &#039;a t&lt;br /&gt;
  val suffixes: &#039;a t -&amp;gt; &#039;a t t&lt;br /&gt;
end;;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;Abstraction de la structure liste chaînée&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Arbre de recherche binaire ===&lt;br /&gt;
&lt;br /&gt;
=== Tas gaucher ===&lt;br /&gt;
&lt;br /&gt;
=== Tas binomial ===&lt;br /&gt;
&lt;br /&gt;
=== Arbre rouge / noir ===&lt;br /&gt;
&lt;br /&gt;
== État actuel du problème ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* [https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf Thèse d&#039;Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf Purely functional data structures, Chris Okasaki]&lt;br /&gt;
&lt;br /&gt;
* [https://fr.wikipedia.org/wiki/Programmation_fonctionnelle Programmation fonctionnelle (Wikipédia)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexes&#039;&#039;&#039;&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_1.png&amp;diff=12524</id>
		<title>Fichier:Schema 1.png</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_1.png&amp;diff=12524"/>
		<updated>2020-05-26T11:13:04Z</updated>

		<summary type="html">&lt;p&gt;Dornel : Dornel a téléversé une nouvelle version de Fichier:Schema 1.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Illustration de la différence entre un schéma éphémère et persistant&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_1.png&amp;diff=12523</id>
		<title>Fichier:Schema 1.png</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Fichier:Schema_1.png&amp;diff=12523"/>
		<updated>2020-05-26T10:47:38Z</updated>

		<summary type="html">&lt;p&gt;Dornel : Illustration de la différence entre un schéma éphémère et persistant&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Illustration de la différence entre un schéma éphémère et persistant&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11710</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11710"/>
		<updated>2019-05-18T12:37:28Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel, et plus particulièrement la correspondance de Curry-Howard dont l&#039;une des caractéristiques est que les propositions sont des types, tandis que les preuves dont des objets.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un projet ayant pour but de réaliser des compilateurs certifiés formellement. Ce projet développe notamment le compilateur CompCert C pour le langage C. Le projet est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project utilise la logique de séparation (formalisée en Coq) pour certifier les propriétés de programmes concurrents.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais si un interpréteur LaTeX la lit, il affiche l&#039;équation précédente. Il existe donc plusieurs façons de présenter la même information en fonction du lecteur visé. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Listes, sacs de nombres et opérations sur ces types ==&lt;br /&gt;
&lt;br /&gt;
=== Listes de nombres ===&lt;br /&gt;
&lt;br /&gt;
Dans Coq, on peut définir une liste de nombres sous le type &#039;&#039;&#039;&#039;&#039;natlist&#039;&#039;&#039;&#039;&#039; de façon récursive. Une liste est composée soit de l&#039;élément vide, soit de la concaténation d&#039;un entier naturel et d&#039;une autre liste. Parmi les différentes opérations que l&#039;on peut exécuter sur les listes, on peut évoquer :&lt;br /&gt;
&lt;br /&gt;
* Créer une liste de longueur déterminée composée d&#039;un seul élément&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint repeat (n count : nat) : natlist :=&lt;br /&gt;
  match count with                     (* Si count vaut... *)&lt;br /&gt;
  | O =&amp;gt; nil                           (* ... 0, on ferme la liste avec l&#039;élément nul *)&lt;br /&gt;
  | S count&#039; =&amp;gt; n :: (repeat n count&#039;) (* ... count&#039; + 1, on concatène le nombre avec la liste de longueur count&#039; répétant ce nombre *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Calculer la longueur d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint length (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; O               (* Cas 1, la liste n&#039;est composée que de l&#039;élément nul, sa longueur est donc nulle *)&lt;br /&gt;
  | h :: t =&amp;gt; S (length t) (* Cas 2, la liste est composée de la concaténation d&#039;un entier et d&#039;une liste, sa longueur vaut donc 1 + la longueur de cette deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Joindre deux listes&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint app (l1 l2 : natlist) : natlist :=&lt;br /&gt;
  match l1 with&lt;br /&gt;
  | nil    =&amp;gt; l2              (* Si la première liste est vide, le résultat de la jonction est donc la seule deuxième liste *)&lt;br /&gt;
  | h :: t =&amp;gt; h :: (app t l2) (* Si la première liste est composée d&#039;un entier et d&#039;une liste, on concatène cet entier avec la jonction de la liste et de la deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Définir la tête et la queue d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition hd (default:nat) (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; default (* Si la liste est vide, on retourne le nombre défini par défaut, l&#039;élément vide n&#039;ayant pas de premier élément *)&lt;br /&gt;
  | h :: t =&amp;gt; h    (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne l&#039;entier, celui-ci étant le premier élément *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition tl (l:natlist) : natlist :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; nil  (* Si la liste est vide, on retourne l&#039;ensemble vide *)&lt;br /&gt;
  | h :: t =&amp;gt; t (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne la liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sacs de nombres ===&lt;br /&gt;
&lt;br /&gt;
Un sac de nombres ressemble à une liste, à l&#039;exception prêt qu&#039;un même élément peut apparaître plusieurs fois. Mis à part ce détail, le type &#039;&#039;&#039;&#039;&#039;bag&#039;&#039;&#039;&#039;&#039; est défini comme une liste de nombres. Parmi les opérations possibles sur ce type, on peut citer :&lt;br /&gt;
&lt;br /&gt;
* Compter le nombre d&#039;occurrence d&#039;un nombre au sein d&#039;un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint count (v:nat) (s:bag) : nat :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; 0                          (* Si la liste est vide, l&#039;élément recherché ne peut pas apparaître *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with       (* Si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier avec l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; S (count v t) (* S&#039;ils sont égaux, on continue la recherche sur le reste de la liste en ajoutant 1 au nombre d&#039;occurrences *)&lt;br /&gt;
              | false =&amp;gt; count v t    (* S&#039;ils ne sont pas égaux, on continue la recherche sur le reste de la liste *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un nombre appartient à un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint member (v:nat) (s:bag) : bool :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; false&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; true&lt;br /&gt;
              | false =&amp;gt; member v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Retirer un ou toutes les occurrences d&#039;un nombre dans un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_one (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil                               (* Si la liste est vide, on n&#039;a plus rien à enlever *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with              (* Similairement à count, si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier à l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; t                    (* S&#039;ils sont égaux, on retourne le reste de la liste, car on a enlevé la première occurrence d&#039;un élément *)&lt;br /&gt;
              | false =&amp;gt; h :: remove_one v t (* S&#039;ils ne sont pas égaux, on concatène l&#039;entier avec le résultat de la recherche au sein de la liste restante *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
remove_all et remove_one fonctionne exactement de la même manière, excepté que remove_all continue la recherche dans tous les cas.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_all (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; remove_all v t&lt;br /&gt;
              | false =&amp;gt; h :: remove_all v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un sac est inclus dans un deuxième sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint subset (s1:bag) (s2:bag) : bool :=&lt;br /&gt;
  match s1 with&lt;br /&gt;
  | nil =&amp;gt; true                                    (* Si le sac de référence est vide, il est forcément inclus dans le deuxième sac *)&lt;br /&gt;
  | h :: t =&amp;gt; match (member h s2) with             (* Si le sac de référence est composée d&#039;un entier et d&#039;une liste, on regarde si cet entier est présent dans le deuxième sac *)&lt;br /&gt;
              | true =&amp;gt; subset t (remove_one h s2) (* S&#039;il est présent, on continue la recherche avec le reste du sac de référence est en retirant une occurrence de l&#039;élément dans le deuxième sac *)&lt;br /&gt;
              | false =&amp;gt; false                     (* S&#039;il n&#039;est pas présent, alors le sac de référence n&#039;est pas inclus dans le deuxième sac *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11696</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11696"/>
		<updated>2019-05-17T14:17:51Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel, et plus particulièrement la correspondance de Curry-Howard dont l&#039;une des caractéristiques est que les propositions sont des types, tandis que les preuves dont des objets.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un projet ayant pour but de réaliser des compilateurs certifiés formellement. Ce projet développe notamment le compilateur CompCert C pour le langage C. Le projet est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais si un interpréteur LaTeX la lit, il affiche l&#039;équation précédente. Il existe donc plusieurs façons de présenter la même information en fonction du lecteur visé. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Listes, sacs de nombres et opérations sur ces types ==&lt;br /&gt;
&lt;br /&gt;
=== Listes de nombres ===&lt;br /&gt;
&lt;br /&gt;
Dans Coq, on peut définir une liste de nombres sous le type &#039;&#039;&#039;&#039;&#039;natlist&#039;&#039;&#039;&#039;&#039; de façon récursive. Une liste est composée soit de l&#039;élément vide, soit de la concaténation d&#039;un entier naturel et d&#039;une autre liste. Parmi les différentes opérations que l&#039;on peut exécuter sur les listes, on peut évoquer :&lt;br /&gt;
&lt;br /&gt;
* Créer une liste de longueur déterminée composée d&#039;un seul élément&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint repeat (n count : nat) : natlist :=&lt;br /&gt;
  match count with                     (* Si count vaut... *)&lt;br /&gt;
  | O =&amp;gt; nil                           (* ... 0, on ferme la liste avec l&#039;élément nul *)&lt;br /&gt;
  | S count&#039; =&amp;gt; n :: (repeat n count&#039;) (* ... count&#039; + 1, on concatène le nombre avec la liste de longueur count&#039; répétant ce nombre *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Calculer la longueur d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint length (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; O               (* Cas 1, la liste n&#039;est composée que de l&#039;élément nul, sa longueur est donc nulle *)&lt;br /&gt;
  | h :: t =&amp;gt; S (length t) (* Cas 2, la liste est composée de la concaténation d&#039;un entier et d&#039;une liste, sa longueur vaut donc 1 + la longueur de cette deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Joindre deux listes&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint app (l1 l2 : natlist) : natlist :=&lt;br /&gt;
  match l1 with&lt;br /&gt;
  | nil    =&amp;gt; l2              (* Si la première liste est vide, le résultat de la jonction est donc la seule deuxième liste *)&lt;br /&gt;
  | h :: t =&amp;gt; h :: (app t l2) (* Si la première liste est composée d&#039;un entier et d&#039;une liste, on concatène cet entier avec la jonction de la liste et de la deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Définir la tête et la queue d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition hd (default:nat) (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; default (* Si la liste est vide, on retourne le nombre défini par défaut, l&#039;élément vide n&#039;ayant pas de premier élément *)&lt;br /&gt;
  | h :: t =&amp;gt; h    (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne l&#039;entier, celui-ci étant le premier élément *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition tl (l:natlist) : natlist :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; nil  (* Si la liste est vide, on retourne l&#039;ensemble vide *)&lt;br /&gt;
  | h :: t =&amp;gt; t (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne la liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sacs de nombres ===&lt;br /&gt;
&lt;br /&gt;
Un sac de nombres ressemble à une liste, à l&#039;exception prêt qu&#039;un même élément peut apparaître plusieurs fois. Mis à part ce détail, le type &#039;&#039;&#039;&#039;&#039;bag&#039;&#039;&#039;&#039;&#039; est défini comme une liste de nombres. Parmi les opérations possibles sur ce type, on peut citer :&lt;br /&gt;
&lt;br /&gt;
* Compter le nombre d&#039;occurrence d&#039;un nombre au sein d&#039;un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint count (v:nat) (s:bag) : nat :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; 0                          (* Si la liste est vide, l&#039;élément recherché ne peut pas apparaître *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with       (* Si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier avec l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; S (count v t) (* S&#039;ils sont égaux, on continue la recherche sur le reste de la liste en ajoutant 1 au nombre d&#039;occurrences *)&lt;br /&gt;
              | false =&amp;gt; count v t    (* S&#039;ils ne sont pas égaux, on continue la recherche sur le reste de la liste *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un nombre appartient à un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint member (v:nat) (s:bag) : bool :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; false&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; true&lt;br /&gt;
              | false =&amp;gt; member v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Retirer un ou toutes les occurrences d&#039;un nombre dans un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_one (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil                               (* Si la liste est vide, on n&#039;a plus rien à enlever *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with              (* Similairement à count, si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier à l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; t                    (* S&#039;ils sont égaux, on retourne le reste de la liste, car on a enlevé la première occurrence d&#039;un élément *)&lt;br /&gt;
              | false =&amp;gt; h :: remove_one v t (* S&#039;ils ne sont pas égaux, on concatène l&#039;entier avec le résultat de la recherche au sein de la liste restante *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
remove_all et remove_one fonctionne exactement de la même manière, excepté que remove_all continue la recherche dans tous les cas.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_all (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; remove_all v t&lt;br /&gt;
              | false =&amp;gt; h :: remove_all v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un sac est inclus dans un deuxième sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint subset (s1:bag) (s2:bag) : bool :=&lt;br /&gt;
  match s1 with&lt;br /&gt;
  | nil =&amp;gt; true                                    (* Si le sac de référence est vide, il est forcément inclus dans le deuxième sac *)&lt;br /&gt;
  | h :: t =&amp;gt; match (member h s2) with             (* Si le sac de référence est composée d&#039;un entier et d&#039;une liste, on regarde si cet entier est présent dans le deuxième sac *)&lt;br /&gt;
              | true =&amp;gt; subset t (remove_one h s2) (* S&#039;il est présent, on continue la recherche avec le reste du sac de référence est en retirant une occurrence de l&#039;élément dans le deuxième sac *)&lt;br /&gt;
              | false =&amp;gt; false                     (* S&#039;il n&#039;est pas présent, alors le sac de référence n&#039;est pas inclus dans le deuxième sac *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11695</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11695"/>
		<updated>2019-05-17T13:35:54Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les formules logiques sont vues comme des cas particuliers d&#039;ensemble ou de types, tandis que les preuves dont des éléments de cet ensemble.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un projet ayant pour but de réaliser des compilateurs certifiés formellement. Ce projet développe notamment le compilateur CompCert C pour le langage C. Le projet est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais si un interpréteur LaTeX la lit, il affiche l&#039;équation précédente. Il existe donc plusieurs façons de présenter la même information en fonction du lecteur visé. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Listes, sacs de nombres et opérations sur ces types ==&lt;br /&gt;
&lt;br /&gt;
=== Listes de nombres ===&lt;br /&gt;
&lt;br /&gt;
Dans Coq, on peut définir une liste de nombres sous le type &#039;&#039;&#039;&#039;&#039;natlist&#039;&#039;&#039;&#039;&#039; de façon récursive. Une liste est composée soit de l&#039;élément vide, soit de la concaténation d&#039;un entier naturel et d&#039;une autre liste. Parmi les différentes opérations que l&#039;on peut exécuter sur les listes, on peut évoquer :&lt;br /&gt;
&lt;br /&gt;
* Créer une liste de longueur déterminée composée d&#039;un seul élément&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint repeat (n count : nat) : natlist :=&lt;br /&gt;
  match count with                     (* Si count vaut... *)&lt;br /&gt;
  | O =&amp;gt; nil                           (* ... 0, on ferme la liste avec l&#039;élément nul *)&lt;br /&gt;
  | S count&#039; =&amp;gt; n :: (repeat n count&#039;) (* ... count&#039; + 1, on concatène le nombre avec la liste de longueur count&#039; répétant ce nombre *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Calculer la longueur d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint length (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; O               (* Cas 1, la liste n&#039;est composée que de l&#039;élément nul, sa longueur est donc nulle *)&lt;br /&gt;
  | h :: t =&amp;gt; S (length t) (* Cas 2, la liste est composée de la concaténation d&#039;un entier et d&#039;une liste, sa longueur vaut donc 1 + la longueur de cette deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Joindre deux listes&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint app (l1 l2 : natlist) : natlist :=&lt;br /&gt;
  match l1 with&lt;br /&gt;
  | nil    =&amp;gt; l2              (* Si la première liste est vide, le résultat de la jonction est donc la seule deuxième liste *)&lt;br /&gt;
  | h :: t =&amp;gt; h :: (app t l2) (* Si la première liste est composée d&#039;un entier et d&#039;une liste, on concatène cet entier avec la jonction de la liste et de la deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Définir la tête et la queue d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition hd (default:nat) (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; default (* Si la liste est vide, on retourne le nombre défini par défaut, l&#039;élément vide n&#039;ayant pas de premier élément *)&lt;br /&gt;
  | h :: t =&amp;gt; h    (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne l&#039;entier, celui-ci étant le premier élément *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition tl (l:natlist) : natlist :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; nil  (* Si la liste est vide, on retourne l&#039;ensemble vide *)&lt;br /&gt;
  | h :: t =&amp;gt; t (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne la liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sacs de nombres ===&lt;br /&gt;
&lt;br /&gt;
Un sac de nombres ressemble à une liste, à l&#039;exception prêt qu&#039;un même élément peut apparaître plusieurs fois. Mis à part ce détail, le type &#039;&#039;&#039;&#039;&#039;bag&#039;&#039;&#039;&#039;&#039; est défini comme une liste de nombres. Parmi les opérations possibles sur ce type, on peut citer :&lt;br /&gt;
&lt;br /&gt;
* Compter le nombre d&#039;occurrence d&#039;un nombre au sein d&#039;un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint count (v:nat) (s:bag) : nat :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; 0                          (* Si la liste est vide, l&#039;élément recherché ne peut pas apparaître *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with       (* Si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier avec l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; S (count v t) (* S&#039;ils sont égaux, on continue la recherche sur le reste de la liste en ajoutant 1 au nombre d&#039;occurrences *)&lt;br /&gt;
              | false =&amp;gt; count v t    (* S&#039;ils ne sont pas égaux, on continue la recherche sur le reste de la liste *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un nombre appartient à un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint member (v:nat) (s:bag) : bool :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; false&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; true&lt;br /&gt;
              | false =&amp;gt; member v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Retirer un ou toutes les occurrences d&#039;un nombre dans un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_one (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil                               (* Si la liste est vide, on n&#039;a plus rien à enlever *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with              (* Similairement à count, si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier à l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; t                    (* S&#039;ils sont égaux, on retourne le reste de la liste, car on a enlevé la première occurrence d&#039;un élément *)&lt;br /&gt;
              | false =&amp;gt; h :: remove_one v t (* S&#039;ils ne sont pas égaux, on concatène l&#039;entier avec le résultat de la recherche au sein de la liste restante *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
remove_all et remove_one fonctionne exactement de la même manière, excepté que remove_all continue la recherche dans tous les cas.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_all (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; remove_all v t&lt;br /&gt;
              | false =&amp;gt; h :: remove_all v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un sac est inclus dans un deuxième sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint subset (s1:bag) (s2:bag) : bool :=&lt;br /&gt;
  match s1 with&lt;br /&gt;
  | nil =&amp;gt; true                                    (* Si le sac de référence est vide, il est forcément inclus dans le deuxième sac *)&lt;br /&gt;
  | h :: t =&amp;gt; match (member h s2) with             (* Si le sac de référence est composée d&#039;un entier et d&#039;une liste, on regarde si cet entier est présent dans le deuxième sac *)&lt;br /&gt;
              | true =&amp;gt; subset t (remove_one h s2) (* S&#039;il est présent, on continue la recherche avec le reste du sac de référence est en retirant une occurrence de l&#039;élément dans le deuxième sac *)&lt;br /&gt;
              | false =&amp;gt; false                     (* S&#039;il n&#039;est pas présent, alors le sac de référence n&#039;est pas inclus dans le deuxième sac *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11681</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11681"/>
		<updated>2019-05-17T10:53:31Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les formules logiques sont vues comme des cas particuliers d&#039;ensemble.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un projet ayant pour but de réaliser des compilateurs certifiés formellement. Ce projet développe notamment le compilateur CompCert C pour le langage C. Le projet est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais si un interpréteur LaTeX la lit, il affiche l&#039;équation précédente. Il existe donc plusieurs façons de présenter la même information en fonction du lecteur visé. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Listes, sacs de nombres et opérations sur ces types ==&lt;br /&gt;
&lt;br /&gt;
=== Listes de nombres ===&lt;br /&gt;
&lt;br /&gt;
Dans Coq, on peut définir une liste de nombres sous le type &#039;&#039;&#039;&#039;&#039;natlist&#039;&#039;&#039;&#039;&#039; de façon récursive. Une liste est composée soit de l&#039;élément vide, soit de la concaténation d&#039;un entier naturel et d&#039;une autre liste. Parmi les différentes opérations que l&#039;on peut exécuter sur les listes, on peut évoquer :&lt;br /&gt;
&lt;br /&gt;
* Créer une liste de longueur déterminée composée d&#039;un seul élément&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint repeat (n count : nat) : natlist :=&lt;br /&gt;
  match count with                     (* Si count vaut... *)&lt;br /&gt;
  | O =&amp;gt; nil                           (* ... 0, on ferme la liste avec l&#039;élément nul *)&lt;br /&gt;
  | S count&#039; =&amp;gt; n :: (repeat n count&#039;) (* ... count&#039; + 1, on concatène le nombre avec la liste de longueur count&#039; répétant ce nombre *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Calculer la longueur d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint length (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; O               (* Cas 1, la liste n&#039;est composée que de l&#039;élément nul, sa longueur est donc nulle *)&lt;br /&gt;
  | h :: t =&amp;gt; S (length t) (* Cas 2, la liste est composée de la concaténation d&#039;un entier et d&#039;une liste, sa longueur vaut donc 1 + la longueur de cette deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Joindre deux listes&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint app (l1 l2 : natlist) : natlist :=&lt;br /&gt;
  match l1 with&lt;br /&gt;
  | nil    =&amp;gt; l2              (* Si la première liste est vide, le résultat de la jonction est donc la seule deuxième liste *)&lt;br /&gt;
  | h :: t =&amp;gt; h :: (app t l2) (* Si la première liste est composée d&#039;un entier et d&#039;une liste, on concatène cet entier avec la jonction de la liste et de la deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Définir la tête et la queue d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition hd (default:nat) (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; default (* Si la liste est vide, on retourne le nombre défini par défaut, l&#039;élément vide n&#039;ayant pas de premier élément *)&lt;br /&gt;
  | h :: t =&amp;gt; h    (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne l&#039;entier, celui-ci étant le premier élément *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition tl (l:natlist) : natlist :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; nil  (* Si la liste est vide, on retourne l&#039;ensemble vide *)&lt;br /&gt;
  | h :: t =&amp;gt; t (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne la liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sacs de nombres ===&lt;br /&gt;
&lt;br /&gt;
Un sac de nombres ressemble à une liste, à l&#039;exception prêt qu&#039;un même élément peut apparaître plusieurs fois. Mis à part ce détail, le type &#039;&#039;&#039;&#039;&#039;bag&#039;&#039;&#039;&#039;&#039; est défini comme une liste de nombres. Parmi les opérations possibles sur ce type, on peut citer :&lt;br /&gt;
&lt;br /&gt;
* Compter le nombre d&#039;occurrence d&#039;un nombre au sein d&#039;un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint count (v:nat) (s:bag) : nat :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; 0                          (* Si la liste est vide, l&#039;élément recherché ne peut pas apparaître *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with       (* Si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier avec l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; S (count v t) (* S&#039;ils sont égaux, on continue la recherche sur le reste de la liste en ajoutant 1 au nombre d&#039;occurrences *)&lt;br /&gt;
              | false =&amp;gt; count v t    (* S&#039;ils ne sont pas égaux, on continue la recherche sur le reste de la liste *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un nombre appartient à un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint member (v:nat) (s:bag) : bool :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; false&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; true&lt;br /&gt;
              | false =&amp;gt; member v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Retirer un ou toutes les occurrences d&#039;un nombre dans un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_one (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil                               (* Si la liste est vide, on n&#039;a plus rien à enlever *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with              (* Similairement à count, si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier à l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; t                    (* S&#039;ils sont égaux, on retourne le reste de la liste, car on a enlevé la première occurrence d&#039;un élément *)&lt;br /&gt;
              | false =&amp;gt; h :: remove_one v t (* S&#039;ils ne sont pas égaux, on concatène l&#039;entier avec le résultat de la recherche au sein de la liste restante *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
remove_all et remove_one fonctionne exactement de la même manière, excepté que remove_all continue la recherche dans tous les cas.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_all (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; remove_all v t&lt;br /&gt;
              | false =&amp;gt; h :: remove_all v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un sac est inclus dans un deuxième sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint subset (s1:bag) (s2:bag) : bool :=&lt;br /&gt;
  match s1 with&lt;br /&gt;
  | nil =&amp;gt; true                                    (* Si le sac de référence est vide, il est forcément inclus dans le deuxième sac *)&lt;br /&gt;
  | h :: t =&amp;gt; match (member h s2) with             (* Si le sac de référence est composée d&#039;un entier et d&#039;une liste, on regarde si cet entier est présent dans le deuxième sac *)&lt;br /&gt;
              | true =&amp;gt; subset t (remove_one h s2) (* S&#039;il est présent, on continue la recherche avec le reste du sac de référence est en retirant une occurrence de l&#039;élément dans le deuxième sac *)&lt;br /&gt;
              | false =&amp;gt; false                     (* S&#039;il n&#039;est pas présent, alors le sac de référence n&#039;est pas inclus dans le deuxième sac *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11577</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11577"/>
		<updated>2019-05-11T13:45:19Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Preuves formelles et informelles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un projet ayant pour but de réaliser des compilateurs certifiés formellement. Ce projet développe notamment le compilateur CompCert C pour le langage C. Le projet est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais si un interpréteur LaTeX la lit, il affiche l&#039;équation précédente. Il existe donc plusieurs façons de présenter la même information en fonction du lecteur visé. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Listes, sacs de nombres et opérations sur ces types ==&lt;br /&gt;
&lt;br /&gt;
=== Listes de nombres ===&lt;br /&gt;
&lt;br /&gt;
Dans Coq, on peut définir une liste de nombres sous le type &#039;&#039;&#039;&#039;&#039;natlist&#039;&#039;&#039;&#039;&#039; de façon récursive. Une liste est composée soit de l&#039;élément vide, soit de la concaténation d&#039;un entier naturel et d&#039;une autre liste. Parmi les différentes opérations que l&#039;on peut exécuter sur les listes, on peut évoquer :&lt;br /&gt;
&lt;br /&gt;
* Créer une liste de longueur déterminée composée d&#039;un seul élément&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint repeat (n count : nat) : natlist :=&lt;br /&gt;
  match count with                     (* Si count vaut... *)&lt;br /&gt;
  | O =&amp;gt; nil                           (* ... 0, on ferme la liste avec l&#039;élément nul *)&lt;br /&gt;
  | S count&#039; =&amp;gt; n :: (repeat n count&#039;) (* ... count&#039; + 1, on concatène le nombre avec la liste de longueur count&#039; répétant ce nombre *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Calculer la longueur d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint length (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; O               (* Cas 1, la liste n&#039;est composée que de l&#039;élément nul, sa longueur est donc nulle *)&lt;br /&gt;
  | h :: t =&amp;gt; S (length t) (* Cas 2, la liste est composée de la concaténation d&#039;un entier et d&#039;une liste, sa longueur vaut donc 1 + la longueur de cette deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Joindre deux listes&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint app (l1 l2 : natlist) : natlist :=&lt;br /&gt;
  match l1 with&lt;br /&gt;
  | nil    =&amp;gt; l2              (* Si la première liste est vide, le résultat de la jonction est donc la seule deuxième liste *)&lt;br /&gt;
  | h :: t =&amp;gt; h :: (app t l2) (* Si la première liste est composée d&#039;un entier et d&#039;une liste, on concatène cet entier avec la jonction de la liste et de la deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Définir la tête et la queue d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition hd (default:nat) (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; default (* Si la liste est vide, on retourne le nombre défini par défaut, l&#039;élément vide n&#039;ayant pas de premier élément *)&lt;br /&gt;
  | h :: t =&amp;gt; h    (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne l&#039;entier, celui-ci étant le premier élément *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition tl (l:natlist) : natlist :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; nil  (* Si la liste est vide, on retourne l&#039;ensemble vide *)&lt;br /&gt;
  | h :: t =&amp;gt; t (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne la liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sacs de nombres ===&lt;br /&gt;
&lt;br /&gt;
Un sac de nombres ressemble à une liste, à l&#039;exception prêt qu&#039;un même élément peut apparaître plusieurs fois. Mis à part ce détail, le type &#039;&#039;&#039;&#039;&#039;bag&#039;&#039;&#039;&#039;&#039; est défini comme une liste de nombres. Parmi les opérations possibles sur ce type, on peut citer :&lt;br /&gt;
&lt;br /&gt;
* Compter le nombre d&#039;occurrence d&#039;un nombre au sein d&#039;un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint count (v:nat) (s:bag) : nat :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; 0                          (* Si la liste est vide, l&#039;élément recherché ne peut pas apparaître *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with       (* Si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier avec l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; S (count v t) (* S&#039;ils sont égaux, on continue la recherche sur le reste de la liste en ajoutant 1 au nombre d&#039;occurrences *)&lt;br /&gt;
              | false =&amp;gt; count v t    (* S&#039;ils ne sont pas égaux, on continue la recherche sur le reste de la liste *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un nombre appartient à un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint member (v:nat) (s:bag) : bool :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; false&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; true&lt;br /&gt;
              | false =&amp;gt; member v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Retirer un ou toutes les occurrences d&#039;un nombre dans un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_one (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil                               (* Si la liste est vide, on n&#039;a plus rien à enlever *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with              (* Similairement à count, si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier à l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; t                    (* S&#039;ils sont égaux, on retourne le reste de la liste, car on a enlevé la première occurrence d&#039;un élément *)&lt;br /&gt;
              | false =&amp;gt; h :: remove_one v t (* S&#039;ils ne sont pas égaux, on concatène l&#039;entier avec le résultat de la recherche au sein de la liste restante *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
remove_all et remove_one fonctionne exactement de la même manière, excepté que remove_all continue la recherche dans tous les cas.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_all (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; remove_all v t&lt;br /&gt;
              | false =&amp;gt; h :: remove_all v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un sac est inclus dans un deuxième sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint subset (s1:bag) (s2:bag) : bool :=&lt;br /&gt;
  match s1 with&lt;br /&gt;
  | nil =&amp;gt; true                                    (* Si le sac de référence est vide, il est forcément inclus dans le deuxième sac *)&lt;br /&gt;
  | h :: t =&amp;gt; match (member h s2) with             (* Si le sac de référence est composée d&#039;un entier et d&#039;une liste, on regarde si cet entier est présent dans le deuxième sac *)&lt;br /&gt;
              | true =&amp;gt; subset t (remove_one h s2) (* S&#039;il est présent, on continue la recherche avec le reste du sac de référence est en retirant une occurrence de l&#039;élément dans le deuxième sac *)&lt;br /&gt;
              | false =&amp;gt; false                     (* S&#039;il n&#039;est pas présent, alors le sac de référence n&#039;est pas inclus dans le deuxième sac *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11576</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11576"/>
		<updated>2019-05-11T13:28:32Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un projet ayant pour but de réaliser des compilateurs certifiés formellement. Ce projet développe notamment le compilateur CompCert C pour le langage C. Le projet est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais un interpréteur LaTeX le lirait comme l&#039;équation précédente. Il existe donc plusieurs façons de présenter la même information en fonction du lecteur visé. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Listes, sacs de nombres et opérations sur ces types ==&lt;br /&gt;
&lt;br /&gt;
=== Listes de nombres ===&lt;br /&gt;
&lt;br /&gt;
Dans Coq, on peut définir une liste de nombres sous le type &#039;&#039;&#039;&#039;&#039;natlist&#039;&#039;&#039;&#039;&#039; de façon récursive. Une liste est composée soit de l&#039;élément vide, soit de la concaténation d&#039;un entier naturel et d&#039;une autre liste. Parmi les différentes opérations que l&#039;on peut exécuter sur les listes, on peut évoquer :&lt;br /&gt;
&lt;br /&gt;
* Créer une liste de longueur déterminée composée d&#039;un seul élément&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint repeat (n count : nat) : natlist :=&lt;br /&gt;
  match count with                     (* Si count vaut... *)&lt;br /&gt;
  | O =&amp;gt; nil                           (* ... 0, on ferme la liste avec l&#039;élément nul *)&lt;br /&gt;
  | S count&#039; =&amp;gt; n :: (repeat n count&#039;) (* ... count&#039; + 1, on concatène le nombre avec la liste de longueur count&#039; répétant ce nombre *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Calculer la longueur d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint length (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; O               (* Cas 1, la liste n&#039;est composée que de l&#039;élément nul, sa longueur est donc nulle *)&lt;br /&gt;
  | h :: t =&amp;gt; S (length t) (* Cas 2, la liste est composée de la concaténation d&#039;un entier et d&#039;une liste, sa longueur vaut donc 1 + la longueur de cette deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Joindre deux listes&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint app (l1 l2 : natlist) : natlist :=&lt;br /&gt;
  match l1 with&lt;br /&gt;
  | nil    =&amp;gt; l2              (* Si la première liste est vide, le résultat de la jonction est donc la seule deuxième liste *)&lt;br /&gt;
  | h :: t =&amp;gt; h :: (app t l2) (* Si la première liste est composée d&#039;un entier et d&#039;une liste, on concatène cet entier avec la jonction de la liste et de la deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Définir la tête et la queue d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition hd (default:nat) (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; default (* Si la liste est vide, on retourne le nombre défini par défaut, l&#039;élément vide n&#039;ayant pas de premier élément *)&lt;br /&gt;
  | h :: t =&amp;gt; h    (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne l&#039;entier, celui-ci étant le premier élément *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition tl (l:natlist) : natlist :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; nil  (* Si la liste est vide, on retourne l&#039;ensemble vide *)&lt;br /&gt;
  | h :: t =&amp;gt; t (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne la liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sacs de nombres ===&lt;br /&gt;
&lt;br /&gt;
Un sac de nombres ressemble à une liste, à l&#039;exception prêt qu&#039;un même élément peut apparaître plusieurs fois. Mis à part ce détail, le type &#039;&#039;&#039;&#039;&#039;bag&#039;&#039;&#039;&#039;&#039; est défini comme une liste de nombres. Parmi les opérations possibles sur ce type, on peut citer :&lt;br /&gt;
&lt;br /&gt;
* Compter le nombre d&#039;occurrence d&#039;un nombre au sein d&#039;un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint count (v:nat) (s:bag) : nat :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; 0                          (* Si la liste est vide, l&#039;élément recherché ne peut pas apparaître *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with       (* Si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier avec l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; S (count v t) (* S&#039;ils sont égaux, on continue la recherche sur le reste de la liste en ajoutant 1 au nombre d&#039;occurrences *)&lt;br /&gt;
              | false =&amp;gt; count v t    (* S&#039;ils ne sont pas égaux, on continue la recherche sur le reste de la liste *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un nombre appartient à un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint member (v:nat) (s:bag) : bool :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; false&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; true&lt;br /&gt;
              | false =&amp;gt; member v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Retirer un ou toutes les occurrences d&#039;un nombre dans un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_one (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil                               (* Si la liste est vide, on n&#039;a plus rien à enlever *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with              (* Similairement à count, si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier à l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; t                    (* S&#039;ils sont égaux, on retourne le reste de la liste, car on a enlevé la première occurrence d&#039;un élément *)&lt;br /&gt;
              | false =&amp;gt; h :: remove_one v t (* S&#039;ils ne sont pas égaux, on concatène l&#039;entier avec le résultat de la recherche au sein de la liste restante *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
remove_all et remove_one fonctionne exactement de la même manière, excepté que remove_all continue la recherche dans tous les cas.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_all (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; remove_all v t&lt;br /&gt;
              | false =&amp;gt; h :: remove_all v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un sac est inclus dans un deuxième sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint subset (s1:bag) (s2:bag) : bool :=&lt;br /&gt;
  match s1 with&lt;br /&gt;
  | nil =&amp;gt; true                                    (* Si le sac de référence est vide, il est forcément inclus dans le deuxième sac *)&lt;br /&gt;
  | h :: t =&amp;gt; match (member h s2) with             (* Si le sac de référence est composée d&#039;un entier et d&#039;une liste, on regarde si cet entier est présent dans le deuxième sac *)&lt;br /&gt;
              | true =&amp;gt; subset t (remove_one h s2) (* S&#039;il est présent, on continue la recherche avec le reste du sac de référence est en retirant une occurrence de l&#039;élément dans le deuxième sac *)&lt;br /&gt;
              | false =&amp;gt; false                     (* S&#039;il n&#039;est pas présent, alors le sac de référence n&#039;est pas inclus dans le deuxième sac *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11575</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11575"/>
		<updated>2019-05-09T15:14:29Z</updated>

		<summary type="html">&lt;p&gt;Dornel : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais un interpréteur LaTeX le lirait comme l&#039;équation précédente. Il existe donc plusieurs façons de présenter la même information en fonction du lecteur visé. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Listes, sacs de nombres et opérations sur ces types ==&lt;br /&gt;
&lt;br /&gt;
=== Listes de nombres ===&lt;br /&gt;
&lt;br /&gt;
Dans Coq, on peut définir une liste de nombres sous le type &#039;&#039;&#039;&#039;&#039;natlist&#039;&#039;&#039;&#039;&#039; de façon récursive. Une liste est composée soit de l&#039;élément vide, soit de la concaténation d&#039;un entier naturel et d&#039;une autre liste. Parmi les différentes opérations que l&#039;on peut exécuter sur les listes, on peut évoquer :&lt;br /&gt;
&lt;br /&gt;
* Créer une liste de longueur déterminée composée d&#039;un seul élément&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint repeat (n count : nat) : natlist :=&lt;br /&gt;
  match count with                     (* Si count vaut... *)&lt;br /&gt;
  | O =&amp;gt; nil                           (* ... 0, on ferme la liste avec l&#039;élément nul *)&lt;br /&gt;
  | S count&#039; =&amp;gt; n :: (repeat n count&#039;) (* ... count&#039; + 1, on concatène le nombre avec la liste de longueur count&#039; répétant ce nombre *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Calculer la longueur d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint length (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; O               (* Cas 1, la liste n&#039;est composée que de l&#039;élément nul, sa longueur est donc nulle *)&lt;br /&gt;
  | h :: t =&amp;gt; S (length t) (* Cas 2, la liste est composée de la concaténation d&#039;un entier et d&#039;une liste, sa longueur vaut donc 1 + la longueur de cette deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Joindre deux listes&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint app (l1 l2 : natlist) : natlist :=&lt;br /&gt;
  match l1 with&lt;br /&gt;
  | nil    =&amp;gt; l2              (* Si la première liste est vide, le résultat de la jonction est donc la seule deuxième liste *)&lt;br /&gt;
  | h :: t =&amp;gt; h :: (app t l2) (* Si la première liste est composée d&#039;un entier et d&#039;une liste, on concatène cet entier avec la jonction de la liste et de la deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Définir la tête et la queue d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition hd (default:nat) (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; default (* Si la liste est vide, on retourne le nombre défini par défaut, l&#039;élément vide n&#039;ayant pas de premier élément *)&lt;br /&gt;
  | h :: t =&amp;gt; h    (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne l&#039;entier, celui-ci étant le premier élément *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition tl (l:natlist) : natlist :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; nil  (* Si la liste est vide, on retourne l&#039;ensemble vide *)&lt;br /&gt;
  | h :: t =&amp;gt; t (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne la liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sacs de nombres ===&lt;br /&gt;
&lt;br /&gt;
Un sac de nombres ressemble à une liste, à l&#039;exception prêt qu&#039;un même élément peut apparaître plusieurs fois. Mis à part ce détail, le type &#039;&#039;&#039;&#039;&#039;bag&#039;&#039;&#039;&#039;&#039; est défini comme une liste de nombres. Parmi les opérations possibles sur ce type, on peut citer :&lt;br /&gt;
&lt;br /&gt;
* Compter le nombre d&#039;occurrence d&#039;un nombre au sein d&#039;un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint count (v:nat) (s:bag) : nat :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; 0                          (* Si la liste est vide, l&#039;élément recherché ne peut pas apparaître *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with       (* Si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier avec l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; S (count v t) (* S&#039;ils sont égaux, on continue la recherche sur le reste de la liste en ajoutant 1 au nombre d&#039;occurrences *)&lt;br /&gt;
              | false =&amp;gt; count v t    (* S&#039;ils ne sont pas égaux, on continue la recherche sur le reste de la liste *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un nombre appartient à un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint member (v:nat) (s:bag) : bool :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; false&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; true&lt;br /&gt;
              | false =&amp;gt; member v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Retirer un ou toutes les occurrences d&#039;un nombre dans un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_one (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil                               (* Si la liste est vide, on n&#039;a plus rien à enlever *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with              (* Similairement à count, si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier à l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; t                    (* S&#039;ils sont égaux, on retourne le reste de la liste, car on a enlevé la première occurrence d&#039;un élément *)&lt;br /&gt;
              | false =&amp;gt; h :: remove_one v t (* S&#039;ils ne sont pas égaux, on concatène l&#039;entier avec le résultat de la recherche au sein de la liste restante *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
remove_all et remove_one fonctionne exactement de la même manière, excepté que remove_all continue la recherche dans tous les cas.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_all (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; remove_all v t&lt;br /&gt;
              | false =&amp;gt; h :: remove_all v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un sac est inclus dans un deuxième sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint subset (s1:bag) (s2:bag) : bool :=&lt;br /&gt;
  match s1 with&lt;br /&gt;
  | nil =&amp;gt; true                                    (* Si le sac de référence est vide, il est forcément inclus dans le deuxième sac *)&lt;br /&gt;
  | h :: t =&amp;gt; match (member h s2) with             (* Si le sac de référence est composée d&#039;un entier et d&#039;une liste, on regarde si cet entier est présent dans le deuxième sac *)&lt;br /&gt;
              | true =&amp;gt; subset t (remove_one h s2) (* S&#039;il est présent, on continue la recherche avec le reste du sac de référence est en retirant une occurrence de l&#039;élément dans le deuxième sac *)&lt;br /&gt;
              | false =&amp;gt; false                     (* S&#039;il n&#039;est pas présent, alors le sac de référence n&#039;est pas inclus dans le deuxième sac *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11568</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11568"/>
		<updated>2019-05-08T11:04:58Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Listes de nombres */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais un interpréteur LaTeX le lirait comme l&#039;équation précédente. Il existe donc plusieurs façons de présenter la même information en fonction du lecteur visé. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Listes, sacs de nombres et opérations sur ces types ==&lt;br /&gt;
&lt;br /&gt;
=== Listes de nombres ===&lt;br /&gt;
&lt;br /&gt;
Dans Coq, on peut définir une liste de nombres sous le type &#039;&#039;&#039;&#039;&#039;natlist&#039;&#039;&#039;&#039;&#039; de façon récursive. Une liste est composée soit de l&#039;élément vide, soit de la concaténation d&#039;un entier naturel et d&#039;une autre liste. Parmi les différentes opérations que l&#039;on peut exécuter sur les listes, on peut évoquer :&lt;br /&gt;
&lt;br /&gt;
* Créer une liste de longueur déterminée composée d&#039;un seul élément&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint repeat (n count : nat) : natlist :=&lt;br /&gt;
  match count with                     (* Si count vaut... *)&lt;br /&gt;
  | O =&amp;gt; nil                           (* ... 0, on ferme la liste avec l&#039;élément nul *)&lt;br /&gt;
  | S count&#039; =&amp;gt; n :: (repeat n count&#039;) (* ... count&#039; + 1, on concatène le nombre avec la liste de longueur count&#039; répétant ce nombre *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Calculer la longueur d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint length (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; O               (* Cas 1, la liste n&#039;est composée que de l&#039;élément nul, sa longueur est donc nulle *)&lt;br /&gt;
  | h :: t =&amp;gt; S (length t) (* Cas 2, la liste est composée de la concaténation d&#039;un entier et d&#039;une liste, sa longueur vaut donc 1 + la longueur de cette deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Joindre deux listes&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint app (l1 l2 : natlist) : natlist :=&lt;br /&gt;
  match l1 with&lt;br /&gt;
  | nil    =&amp;gt; l2              (* Si la première liste est vide, le résultat de la jonction est donc la seule deuxième liste *)&lt;br /&gt;
  | h :: t =&amp;gt; h :: (app t l2) (* Si la première liste est composée d&#039;un entier et d&#039;une liste, on concatène cet entier avec la jonction de la liste et de la deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Définir la tête et la queue d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition hd (default:nat) (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; default (* Si la liste est vide, on retourne le nombre défini par défaut, l&#039;élément vide n&#039;ayant pas de premier élément *)&lt;br /&gt;
  | h :: t =&amp;gt; h    (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne l&#039;entier, celui-ci étant le premier élément *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition tl (l:natlist) : natlist :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; nil  (* Si la liste est vide, on retourne l&#039;ensemble vide *)&lt;br /&gt;
  | h :: t =&amp;gt; t (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne la liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sacs de nombres ===&lt;br /&gt;
&lt;br /&gt;
Un sac de nombres ressemble à une liste, à l&#039;exception prêt qu&#039;un même élément peut apparaître plusieurs fois. Mis à part ce détail, le type &#039;&#039;&#039;&#039;&#039;bag&#039;&#039;&#039;&#039;&#039; est défini comme une liste de nombres. Parmi les opérations possibles sur ce type, on peut citer :&lt;br /&gt;
&lt;br /&gt;
* Compter le nombre d&#039;occurrence d&#039;un nombre au sein d&#039;un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint count (v:nat) (s:bag) : nat :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; 0                          (* Si la liste est vide, l&#039;élément recherché ne peut pas apparaître *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with       (* Si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier avec l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; S (count v t) (* S&#039;ils sont égaux, on continue la recherche sur le reste de la liste en ajoutant 1 au nombre d&#039;occurrences *)&lt;br /&gt;
              | false =&amp;gt; count v t    (* S&#039;ils ne sont pas égaux, on continue la recherche sur le reste de la liste *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un nombre appartient à un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint member (v:nat) (s:bag) : bool :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; false&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; true&lt;br /&gt;
              | false =&amp;gt; member v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Retirer un ou toutes les occurrences d&#039;un nombre dans un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_one (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil                               (* Si la liste est vide, on n&#039;a plus rien à enlever *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with              (* Similairement à count, si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier à l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; t                    (* S&#039;ils sont égaux, on retourne le reste de la liste, car on a enlevé la première occurrence d&#039;un élément *)&lt;br /&gt;
              | false =&amp;gt; h :: remove_one v t (* S&#039;ils ne sont pas égaux, on concatène l&#039;entier avec le résultat de la recherche au sein de la liste restante *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
remove_all et remove_one fonctionne exactement de la même manière, excepté que remove_all continue la recherche dans tous les cas.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_all (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; remove_all v t&lt;br /&gt;
              | false =&amp;gt; h :: remove_all v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un sac est inclus dans un deuxième sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint subset (s1:bag) (s2:bag) : bool :=&lt;br /&gt;
  match s1 with&lt;br /&gt;
  | nil =&amp;gt; true                                    (* Si le sac de référence est vide, il est forcément inclus dans le deuxième sac *)&lt;br /&gt;
  | h :: t =&amp;gt; match (member h s2) with             (* Si le sac de référence est composée d&#039;un entier et d&#039;une liste, on regarde si cet entier est présent dans le deuxième sac *)&lt;br /&gt;
              | true =&amp;gt; subset t (remove_one h s2) (* S&#039;il est présent, on continue la recherche avec le reste du sac de référence est en retirant une occurrence de l&#039;élément dans le deuxième sac *)&lt;br /&gt;
              | false =&amp;gt; false                     (* S&#039;il n&#039;est pas présent, alors le sac de référence n&#039;est pas inclus dans le deuxième sac *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11567</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11567"/>
		<updated>2019-05-08T11:03:47Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Paires, listes et sacs de nombres */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais un interpréteur LaTeX le lirait comme l&#039;équation précédente. Il existe donc plusieurs façons de présenter la même information en fonction du lecteur visé. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Listes, sacs de nombres et opérations sur ces types ==&lt;br /&gt;
&lt;br /&gt;
=== Listes de nombres ===&lt;br /&gt;
&lt;br /&gt;
Dans Coq, on peut définir une liste de nombres sous le type &#039;&#039;&#039;&#039;&#039;natlistx&#039;&#039;&#039;&#039;&#039; de façon récursive. Une liste est composée soit de l&#039;élément vide, soit de la concaténation d&#039;un entier naturel et d&#039;une autre liste. Parmi les différentes opérations que l&#039;on peut exécuter sur les listes, on peut évoquer :&lt;br /&gt;
&lt;br /&gt;
* Créer une liste de longueur déterminée composée d&#039;un seul élément&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint repeat (n count : nat) : natlist :=&lt;br /&gt;
  match count with                     (* Si count vaut... *)&lt;br /&gt;
  | O =&amp;gt; nil                           (* ... 0, on ferme la liste avec l&#039;élément nul *)&lt;br /&gt;
  | S count&#039; =&amp;gt; n :: (repeat n count&#039;) (* ... count&#039; + 1, on concatène le nombre avec la liste de longueur count&#039; répétant ce nombre *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Calculer la longueur d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint length (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; O               (* Cas 1, la liste n&#039;est composée que de l&#039;élément nul, sa longueur est donc nulle *)&lt;br /&gt;
  | h :: t =&amp;gt; S (length t) (* Cas 2, la liste est composée de la concaténation d&#039;un entier et d&#039;une liste, sa longueur vaut donc 1 + la longueur de cette deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Joindre deux listes&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint app (l1 l2 : natlist) : natlist :=&lt;br /&gt;
  match l1 with&lt;br /&gt;
  | nil    =&amp;gt; l2              (* Si la première liste est vide, le résultat de la jonction est donc la seule deuxième liste *)&lt;br /&gt;
  | h :: t =&amp;gt; h :: (app t l2) (* Si la première liste est composée d&#039;un entier et d&#039;une liste, on concatène cet entier avec la jonction de la liste et de la deuxième liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Définir la tête et la queue d&#039;une liste&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition hd (default:nat) (l:natlist) : nat :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; default (* Si la liste est vide, on retourne le nombre défini par défaut, l&#039;élément vide n&#039;ayant pas de premier élément *)&lt;br /&gt;
  | h :: t =&amp;gt; h    (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne l&#039;entier, celui-ci étant le premier élément *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition tl (l:natlist) : natlist :=&lt;br /&gt;
  match l with&lt;br /&gt;
  | nil =&amp;gt; nil  (* Si la liste est vide, on retourne l&#039;ensemble vide *)&lt;br /&gt;
  | h :: t =&amp;gt; t (* Si la liste est composée d&#039;un entier et d&#039;une liste, on retourne la liste *)&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Sacs de nombres ===&lt;br /&gt;
&lt;br /&gt;
Un sac de nombres ressemble à une liste, à l&#039;exception prêt qu&#039;un même élément peut apparaître plusieurs fois. Mis à part ce détail, le type &#039;&#039;&#039;&#039;&#039;bag&#039;&#039;&#039;&#039;&#039; est défini comme une liste de nombres. Parmi les opérations possibles sur ce type, on peut citer :&lt;br /&gt;
&lt;br /&gt;
* Compter le nombre d&#039;occurrence d&#039;un nombre au sein d&#039;un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint count (v:nat) (s:bag) : nat :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; 0                          (* Si la liste est vide, l&#039;élément recherché ne peut pas apparaître *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with       (* Si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier avec l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; S (count v t) (* S&#039;ils sont égaux, on continue la recherche sur le reste de la liste en ajoutant 1 au nombre d&#039;occurrences *)&lt;br /&gt;
              | false =&amp;gt; count v t    (* S&#039;ils ne sont pas égaux, on continue la recherche sur le reste de la liste *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un nombre appartient à un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint member (v:nat) (s:bag) : bool :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; false&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; true&lt;br /&gt;
              | false =&amp;gt; member v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Retirer un ou toutes les occurrences d&#039;un nombre dans un sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_one (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil                               (* Si la liste est vide, on n&#039;a plus rien à enlever *)&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with              (* Similairement à count, si la liste est composée d&#039;un entier et d&#039;une liste, on compare l&#039;entier à l&#039;élément recherché *)&lt;br /&gt;
              | true =&amp;gt; t                    (* S&#039;ils sont égaux, on retourne le reste de la liste, car on a enlevé la première occurrence d&#039;un élément *)&lt;br /&gt;
              | false =&amp;gt; h :: remove_one v t (* S&#039;ils ne sont pas égaux, on concatène l&#039;entier avec le résultat de la recherche au sein de la liste restante *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
remove_all et remove_one fonctionne exactement de la même manière, excepté que remove_all continue la recherche dans tous les cas.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint remove_all (v:nat) (s:bag) : bag :=&lt;br /&gt;
  match s with&lt;br /&gt;
  | nil =&amp;gt; nil&lt;br /&gt;
  | h :: t =&amp;gt; match h =? v with&lt;br /&gt;
              | true =&amp;gt; remove_all v t&lt;br /&gt;
              | false =&amp;gt; h :: remove_all v t&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Vérifier qu&#039;un sac est inclus dans un deuxième sac&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint subset (s1:bag) (s2:bag) : bool :=&lt;br /&gt;
  match s1 with&lt;br /&gt;
  | nil =&amp;gt; true                                    (* Si le sac de référence est vide, il est forcément inclus dans le deuxième sac *)&lt;br /&gt;
  | h :: t =&amp;gt; match (member h s2) with             (* Si le sac de référence est composée d&#039;un entier et d&#039;une liste, on regarde si cet entier est présent dans le deuxième sac *)&lt;br /&gt;
              | true =&amp;gt; subset t (remove_one h s2) (* S&#039;il est présent, on continue la recherche avec le reste du sac de référence est en retirant une occurrence de l&#039;élément dans le deuxième sac *)&lt;br /&gt;
              | false =&amp;gt; false                     (* S&#039;il n&#039;est pas présent, alors le sac de référence n&#039;est pas inclus dans le deuxième sac *)&lt;br /&gt;
              end&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11566</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11566"/>
		<updated>2019-05-08T08:44:26Z</updated>

		<summary type="html">&lt;p&gt;Dornel : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais un interpréteur LaTeX le lirait comme l&#039;équation précédente. Il existe donc plusieurs façons de présenter la même information en fonction du lecteur visé. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Paires, listes et sacs de nombres ==&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11565</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11565"/>
		<updated>2019-05-07T14:54:22Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Preuves formelles et informelles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais un interpréteur LaTeX le lirait comme l&#039;équation précédente. Enfin, il est également possible d&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
À un ordre n et en un point a donnés, une fonction n fois dérivables au voisinage de a peut se décomposer en une fonction polynomiale dont les coefficients sont les quotients des dérivées d&#039;ordre entre 0 et n en a par la factorielle de l&#039;ordre dont le reste est négligeable devant la variable de plus haut degré en a.&lt;br /&gt;
&lt;br /&gt;
Cette dernière formulation peut être difficile à interpréter, autant par un humain que par une machine, mais équivaut aux deux premières. Il en est de même pour les preuves sur Coq.&lt;br /&gt;
&lt;br /&gt;
* Preuves formelles&lt;br /&gt;
&lt;br /&gt;
Les preuves formelles sont celles que Coq peut comprendre facilement et qui n&#039;est pas prévue pour être facilement comprise par un humain. Par exemple, la preuve formelle de l&#039;associativité de l&#039;addition est structurée comme suit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. intros n m p. induction n as [| n&#039; IHn&#039;]. reflexivity. simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.  Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le code est réduit à l&#039;essentiel, mais la clarté pour le lecteur humain est moindre.&lt;br /&gt;
&lt;br /&gt;
* Preuves informelles&lt;br /&gt;
&lt;br /&gt;
Les preuves informelles sont celles que Coq accepte et qu&#039;un humain peut comprendre facilement. Par exemple, la preuve informelle de l&#039;associativité de l&#039;addition est structurée comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m p. induction n as [| n&#039; IHn&#039;].&lt;br /&gt;
  - (* n = 0 *)&lt;br /&gt;
    reflexivity.&lt;br /&gt;
  - (* n = S n&#039; *)&lt;br /&gt;
    simpl. rewrite -&amp;gt; IHn&#039;. reflexivity.&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, on a éclaté le code pour notamment séparer les deux cas étudiés lors de la récurrence et on a ajouté des commentaires. Coq interprète toujours le code, mais un lecteur humain peut davantage comprendre ce qui se passe.&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11564</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11564"/>
		<updated>2019-05-07T14:32:18Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Preuves formelles et informelles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;latex&amp;quot;&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais un interpréteur LaTeX le lirait comme l&#039;équation précédente.&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11563</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11563"/>
		<updated>2019-05-07T14:32:00Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Preuves formelles et informelles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cette écriture peut être lue aisément par un humain, mais pas par une machine. Cependant, si l&#039;on écrit :&lt;br /&gt;
&lt;br /&gt;
f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&lt;br /&gt;
&lt;br /&gt;
Un humain aurait du mal à lire cette ligne de code, mais un interpréteur LaTeX le lirait comme l&#039;équation précédente.&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11562</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11562"/>
		<updated>2019-05-07T14:29:47Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Preuves formelles et informelles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
Le principe d&#039;une preuve est de convaincre le lecteur de sa véracité. Cependant, tous les lecteurs n&#039;ont pas la même capacité de compréhension.&lt;br /&gt;
&lt;br /&gt;
Par exemple, en langage mathématique, le théorème de Taylor à l&#039;ordre n en a pourrait s&#039;écrire :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f(x) = \sum_{k=0}^{n}{\frac{f^{(k)}(a)}{k!}{x - a}^{k}} + R_{n}(x), \lim_{x \to a} R_{n}(x)= 0&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11561</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11561"/>
		<updated>2019-05-07T14:20:51Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Preuve par récurrence, preuves à l&amp;#039;intérieur de preuves, preuves formelles et informelles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Preuves formelles et informelles ===&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11560</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11560"/>
		<updated>2019-05-07T14:20:08Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Tactiques */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11559</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11559"/>
		<updated>2019-05-07T14:19:16Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Preuve par récurrence */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;. *)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial. *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent. Il est également nécessaire d&#039;utiliser à un moment l&#039;hypothèse d&#039;hérédité avec rewrite. S&#039;il est possible de compléter la preuve sans l&#039;utiliser, une autre tactique aurait été probablement plus appropriée.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11558</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11558"/>
		<updated>2019-05-07T14:17:35Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Preuves à l&amp;#039;intérieur de preuves */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;*)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. Il est nécessaire à un moment d&#039;utiliser l&#039;hypothèse d&#039;hérédité avec rewrite. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;. Par exemple, la preuve servant à démontrer que &amp;lt;math&amp;gt;\forall n,m \in \mathbb{R}, (0 + n) * m = n * m&amp;lt;/math&amp;gt; se structure comme tel :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;Coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n m.            (* On introduit les deux variables n et m *)&lt;br /&gt;
  assert (H: 0 + n = n). (* On définit le lemme H qui dit que 0 + n = n *)&lt;br /&gt;
    { reflexivity. }     (* On vérifie que H est vrai *)&lt;br /&gt;
  rewrite -&amp;gt; H.          (* On reformule l&#039;équation initiale en remplaçant 0 + n par n, maintenant que l&#039;on sait que les deux sont égaux *)&lt;br /&gt;
  reflexivity.           (* Les deux membres de l&#039;équation sont alors identiques, la preuve est achevée *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11557</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11557"/>
		<updated>2019-05-07T14:12:30Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Preuve par récurrence, preuves à l&amp;#039;intérieur de preuves, preuves formelles et informelles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;*)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. Il est nécessaire à un moment d&#039;utiliser l&#039;hypothèse d&#039;hérédité avec rewrite. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ben que la récurrence soit une tactique puissante, il vaut mieux la réserver aux situations qui le nécessitent.&lt;br /&gt;
&lt;br /&gt;
=== Preuves à l&#039;intérieur de preuves ===&lt;br /&gt;
&lt;br /&gt;
Parfois, il n&#039;est pas possible de démontrer une équation telle quelle. Il faut passer par un lemme qui est introduit en Coq par l&#039;instruction &#039;&#039;&#039;&#039;&#039;assert&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
	<entry>
		<id>http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11556</id>
		<title>Initiation à la démonstration sur ordinateur et certification de logiciel</title>
		<link rel="alternate" type="text/html" href="http://os-vps418.infomaniak.ch:1250/mediawiki/index.php?title=Initiation_%C3%A0_la_d%C3%A9monstration_sur_ordinateur_et_certification_de_logiciel&amp;diff=11556"/>
		<updated>2019-05-06T12:59:11Z</updated>

		<summary type="html">&lt;p&gt;Dornel : /* Sources et annexes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Coq est un logiciel d&#039;aide à la preuve mathématique développé par les équipes PI.R2 et Marelle d&#039;Inria et l&#039;équipe Systèmes Sûrs du Cnam. La première version a été publiée en 1984 et a été codée sous CAML.&lt;br /&gt;
&lt;br /&gt;
Coq est fondé sur le calcul des constructions, une théorie concurrente à la théorie des ensembles de Zermelo-Fraenkel dont l&#039;une des particularités est que les preuves sont au même niveau que les fonctions.&lt;br /&gt;
&lt;br /&gt;
Parmi les théorèmes issus des mathématiques dont les preuves sont volumineuses et qui ont été démontrées à l&#039;aide de Coq, on peut citer notamment :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème des quatre couleurs&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème des quatre couleurs indique qu&#039;il est possible en utilisant seulement quatre couleurs différentes de colorier n&#039;importe quelle carte découpée en régions de sorte que deux régions ayant une frontière en commun ne soient pas de la même couleur. Le résultat fut initialement conjecturé en 1852, avec les deux premières preuves étant publiées en 1879 et 1880, celles-ci se révélant cependant fausses. La première preuve utilisant l&#039;outil informatique date de 1976 et fut reprise et simplifiée par la suite. Enfin, en 2005, Georges Gonthier et Benjamin Werner ont réussi à formuler avec Coq une preuve formelle permettant à un ordinateur de complètement vérifier le théorème des quatre couleurs. À ce jour, aucune preuve qui ne fasse pas appel à un ordinateur n&#039;a été découverte.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Théorème de Feit-Thompson&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Le théorème de Feit-Thompson énonce que tout groupe fini d&#039;ordre impair est résoluble, ce qui équivaut à dire que tout groupe simple fini non commutatif est d&#039;ordre pair. Le théorème fut conjecturé en 1911 par William Burnside et démontré en 1963 par Walter Feit et John Griggs Thompson. Une formalisation de la preuve en Coq a été achevée en 2012 par Georges Gonthier et son équipe du laboratoire commun Inria-Microsoft.&lt;br /&gt;
&lt;br /&gt;
Coq sert également à la certification de logiciel. Parmi la multitude d&#039;exemples, on peut mentionner :&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;CompCert&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
CompCert est un compilateur pour le langage C qui utilise des preuves formelles pour vérifier le code. Cela est réalisé notamment en Coq.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Iris Project&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iris Project est un logiciel de séparation logique simultanée d&#039;ordre supérieure (&#039;&#039;demander informations complémentaires&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
Oral :&lt;br /&gt;
&lt;br /&gt;
Software Foundation (pédagogique :wave:)&lt;br /&gt;
VST (Verified Software Toolchain)&lt;br /&gt;
fscq&lt;br /&gt;
Bedrock&lt;br /&gt;
CFML&lt;br /&gt;
&lt;br /&gt;
Conclusion :&lt;br /&gt;
Je n&#039;ai pas pu couvrir qu&#039;une fraction des fonctionnalités proposées par Coq, mais j&#039;ai quand même atteint l&#039;objectif que mon tuteur avait fixé.&lt;br /&gt;
&lt;br /&gt;
== Types, fonctions et preuves basiques ==&lt;br /&gt;
&lt;br /&gt;
=== Déclaration de types ===&lt;br /&gt;
&lt;br /&gt;
Pour définir un type sur Coq, une première méthode est l&#039;induction. Cela consiste à utiliser les différents cas particuliers pour définir le cas général.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive day : Type :=&lt;br /&gt;
  | monday&lt;br /&gt;
  | tuesday&lt;br /&gt;
  | wednesday&lt;br /&gt;
  | thursday&lt;br /&gt;
  | friday&lt;br /&gt;
  | saturday&lt;br /&gt;
  | sunday.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ici, pour le type &#039;&#039;day&#039;&#039;, on énumère les différentes valeurs que peut adopter le type, à savoir les jours de la semaine. Cette méthode permet notamment de définir des types dont l&#039;ensemble de valeurs possibles est fini, comme les booléens ou les bits. Cependant, tenter de représenter les nombres avec cette méthode est contre-indiqué, car il faudrait un temps et une capacité de stockage disproportionnellement élevés.&lt;br /&gt;
&lt;br /&gt;
Pour définir les nombres ou d&#039;autres types dont l&#039;ensemble de valeurs possibles est infini, il est possible de définir un type par récurrence.&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive nat : Type:=&lt;br /&gt;
  | O&lt;br /&gt;
  | S (n : nat).&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ce cas-là, par exemple, on définit le type &#039;&#039;nat&#039;&#039; (correspondant aux entiers naturels) par un cas initial, ici 0, modélisé par la lettre O, et une hérédité, ici le fait que pour tout n ∈ ℕ, il existe un n&#039; appartenant à ce même intervalle tel que n corresponde à S n&#039;. L&#039;écriture mathématique équivalente de cette définition est :&lt;br /&gt;
&amp;lt;math&amp;gt;\forall n &amp;gt; 0, \exists n&#039; \geqslant 0, n = n&#039; + 1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enfin, il est également possible avec Coq de créer un type utilisant un autre type pour sa définition. Ainsi, si l&#039;on définit comme suit les teintes RGB d&#039;une couleur :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive rgb : Type :=&lt;br /&gt;
  | red&lt;br /&gt;
  | green&lt;br /&gt;
  | blue.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Il est possible de définir les composantes d&#039;une couleur en utilisant le type &#039;&#039;rgb&#039;&#039; préalablement créé :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Inductive color : Type :=&lt;br /&gt;
  | black&lt;br /&gt;
  | white&lt;br /&gt;
  | primary (p : rgb).&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ainsi, &#039;&#039;color&#039;&#039; possède trois composantes, à savoir black, white et primary, cette dernière ayant elle-même trois composantes, red, green et blue.&lt;br /&gt;
&lt;br /&gt;
=== Fonctions ===&lt;br /&gt;
&lt;br /&gt;
Il y a plusieurs manières de définir une fonction dans Coq. La première est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Definition&#039;&#039;&#039;&#039;&#039; et de spécifier au cas par cas quel résultat la fonction retourne selon la valeur de la variable d&#039;entrée.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Ainsi, en reprenant le type &#039;&#039;day&#039;&#039;, on peut définir la fonction next_weekday comme suit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition next_weekday (d:day) : day :=&lt;br /&gt;
  match d with&lt;br /&gt;
  | monday    =&amp;gt; tuesday&lt;br /&gt;
  | tuesday   =&amp;gt; wednesday&lt;br /&gt;
  | wednesday =&amp;gt; thursday&lt;br /&gt;
  | thursday  =&amp;gt; friday&lt;br /&gt;
  | friday    =&amp;gt; saturday&lt;br /&gt;
  | saturday  =&amp;gt; sunday&lt;br /&gt;
  | sunday    =&amp;gt; monday&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Dans cet exemple, on introduit le nom de la fonction, on indique le nom utilisé en son sein pour désigner la variable ainsi que son type, puis le type du résultat.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
L&#039;instruction &#039;&#039;match d with&#039;&#039; sert à analyser la valeur de la variable d. La liste qui suit signifie que pour telle valeur de d (par exemple wednesday), on retourne le résultat indiqué par la flèche (ici thursday). Enfin, la commande &#039;&#039;end&#039;&#039; permet de délimiter la fin de la fonction.&lt;br /&gt;
&lt;br /&gt;
Cependant, il est également possible de ne retenir que certains cas et de retourner une même réponse pour tous les cas non vérifiés, dans la même veine qu&#039;un &#039;&#039;else&#039;&#039; dans d&#039;autres langages.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
Par exemple, en supposant que l&#039;on a défini précédemment un type &#039;&#039;bool&#039;&#039; qui ne peut prendre que true et false comme valeurs, on peut établir la fonction suivante :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Definition isred (c : color) : bool :=&lt;br /&gt;
  match c with&lt;br /&gt;
  | black =&amp;gt; false&lt;br /&gt;
  | white =&amp;gt; false&lt;br /&gt;
  | primary red =&amp;gt; true&lt;br /&gt;
  | primary _ =&amp;gt; false&lt;br /&gt;
  end.&amp;lt;/pre&amp;gt;&lt;br /&gt;
Ici, si l&#039;on voulait écrire la fonction en Python, on aurait :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;python&amp;quot;&amp;gt;def isred(c) :&lt;br /&gt;
    if c == black :&lt;br /&gt;
        return False&lt;br /&gt;
    elif c == white :&lt;br /&gt;
        return False :&lt;br /&gt;
    elif c == primary[red] :&lt;br /&gt;
        return True&lt;br /&gt;
    else :&lt;br /&gt;
        return False&amp;lt;/pre&amp;gt;&lt;br /&gt;
On remarque donc que le caractère _ signifie &amp;quot;dans tous les autres cas possibles&amp;quot;, ce qui permet de ne retenir que les situations particulières et de généraliser le reste.&lt;br /&gt;
&lt;br /&gt;
Une autre manière d&#039;écrire une fonction est d&#039;utiliser la commande &#039;&#039;&#039;&#039;&#039;Fixpoint&#039;&#039;&#039;&#039;&#039;. La différence majeure entre Fixpoint et Definition est la possibilité d&#039;appeler la fonction récursivement avec Fixpoint.&lt;br /&gt;
&lt;br /&gt;
Par exemple, une fonction pour définir si un entier naturel est pair s&#039;écrit :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Fixpoint evenb(n:nat) : bool :=&lt;br /&gt;
  match n with&lt;br /&gt;
  | 0 =&amp;gt; true&lt;br /&gt;
  | S 0 =&amp;gt; false&lt;br /&gt;
  | S (S n&#039;) =&amp;gt; evenb n&#039;&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cela signifie que pour tout entier n, trois cas de figure se présentent :&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 0 \rightarrow n\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; n = 1 \rightarrow n\ non\ pair &amp;lt;/math&amp;gt;&lt;br /&gt;
# &amp;lt;math&amp;gt; \exists n&#039;, n = n&#039; + 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
Dans le troisième cas, on réitère l&#039;étude, mais avec n&#039;.&lt;br /&gt;
&lt;br /&gt;
Enfin, une troisième méthode pour définir une fonction est la commande &#039;&#039;&#039;&#039;&#039;Theorem&#039;&#039;&#039;&#039;&#039; qui permet, comme son nom l&#039;indique, de définir un théorème, généralement sous la forme d&#039;une égalité ou d&#039;une implication. S&#039;il s&#039;agit d&#039;une implication A -&amp;gt; B, A peut être utilisé comme hypothèse lors de la preuve.&lt;br /&gt;
&lt;br /&gt;
=== Preuves simples ===&lt;br /&gt;
&lt;br /&gt;
==== Structure générale ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, la structure d&#039;une preuve suit toujours la même structure :&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof. (* On indique le début de la preuve... *)&lt;br /&gt;
(* On insère ici toutes les instructions nécessaires... *)&lt;br /&gt;
Qed. (* Quod erat demonstrandum, équivalent latin de C.Q.F.D., signifie que la preuve est finie *)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tactiques ====&lt;br /&gt;
&lt;br /&gt;
Les tactiques sont des instructions pour aider Coq à résoudre une preuve.&lt;br /&gt;
&lt;br /&gt;
* simpl.&lt;br /&gt;
&lt;br /&gt;
simpl. permet, comme son nom l&#039;indique, de simplifier une formule.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* reflexivity.&lt;br /&gt;
&lt;br /&gt;
reflexivity. permet de vérifier si une égalité est du type a = a. Si la réflexivité de l&#039;égalité est prouvée, alors la proposition est vraie. Cette instruction permet généralement d&#039;achever une preuve.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* intros n.&lt;br /&gt;
&lt;br /&gt;
intros. s&#039;utilise en début de preuve pour introduire les variables présentes dans un théorème ou une hypothèse. S&#039;il y a plusieurs variables à introduire, il faut les séparer par des espaces&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* unfold f.&lt;br /&gt;
&lt;br /&gt;
unfold f. permet de développer l&#039;expression à étudier selon la fonction f.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* rewrite &amp;lt;- / -&amp;gt; H.&lt;br /&gt;
&lt;br /&gt;
Permet de réécrire l&#039;expression en fonction de l&#039;hypothèse H. La flèche &amp;lt;- indique que l&#039;on souhaite passer du membre de droite à celui de gauche et inversement pour -&amp;gt;. Coq va ensuite essayer de trouver un membre de l&#039;expression qui correspond au terme de départ et va le remplacer par le terme d&#039;arrivée.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* destruct n as [n1 | n2 | ...] eqn:E.&lt;br /&gt;
&lt;br /&gt;
destruct permet de décomposer une variable en plusieurs cas de figure qui sont traités séparément. destruct fonctionne différemment selon le type de la variable utilisée. Avec les booléens ou d&#039;autres types binaires, on peut se passer du as [...] car il n&#039;y a par définition que deux valeurs que peut prendre la variable. Avec les entiers, on va généralement décomposer sous la forme as [| n&#039;], autrement dit, on établit une pseudo-récurrence en vérifiant pour n = 0 et ensuite pour tout n&#039; tel que n = n&#039; + 1. Il ne s&#039;agit cependant pas d&#039;une vraie récurrence car il n&#039;y a pas de lien entre n et n&#039;.&lt;br /&gt;
&#039;&#039;insérer détail et exemple ici&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== Séparation des cas ====&lt;br /&gt;
&lt;br /&gt;
Dans Coq, certaines commandes comme destruct occasionnent une séparation de l&#039;expression en plusieurs cas. Pour identifier ces cas, il y a deux méthodes.&lt;br /&gt;
&lt;br /&gt;
# Commencer chaque cas par une puce (-, + et * dans cet ordre)&lt;br /&gt;
# Délimiter un cas par des astérisque {}, ce qui permet de réinitialiser la hiérarchie des puces&lt;br /&gt;
&lt;br /&gt;
== Preuve par récurrence, preuves à l&#039;intérieur de preuves, preuves formelles et informelles ==&lt;br /&gt;
&lt;br /&gt;
=== Preuve par récurrence ===&lt;br /&gt;
&lt;br /&gt;
Bien que les tactiques mentionnées dans la partie précédente permettent de résoudre une grande partie des preuves, certains cas requièrent une attention plus particulière. Si la situation s&#039;y prête, on peut notamment utiliser la récurrence avec la syntaxe suivante :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre lang=&amp;quot;coq&amp;quot;&amp;gt;Proof.&lt;br /&gt;
  intros n. induction n as [| n&#039; IHn&#039;]. (* On décompose n en un cas initial, généralement 0, et on introduit l&#039;hypothèse que la proposition est vérifiée pour un palier arbitraire n&#039;*)&lt;br /&gt;
  - (* Initialisation. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée pour le cas initial *)&lt;br /&gt;
  - (* Hérédité. On met ici toutes les tactiques nécessaires pour prouver que la proposition est vérifiée à un niveau n&#039; + 1 si elle est vérifiée pour n&#039;. Il est nécessaire à un moment d&#039;utiliser l&#039;hypothèse d&#039;hérédité avec rewrite. *)&lt;br /&gt;
Qed.&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sources et annexes ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sources&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Software Foundations volume 1 [https://softwarefoundations.cis.upenn.edu/lf-current/index.html]&lt;br /&gt;
&lt;br /&gt;
* Page Wikipédia de Coq [https://fr.wikipedia.org/wiki/Coq_(logiciel)]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Annexe&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Théorème des quatre couleurs [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_des_quatre_couleurs]&lt;br /&gt;
&lt;br /&gt;
* Théorème de Feit-Thompson [https://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Feit-Thompson]&lt;br /&gt;
&lt;br /&gt;
* Compcert [http://compcert.inria.fr/]&lt;br /&gt;
&lt;br /&gt;
* Iris Project [https://iris-project.org/]&lt;br /&gt;
&lt;br /&gt;
* Software Foundations [https://softwarefoundations.cis.upenn.edu/]&lt;br /&gt;
&lt;br /&gt;
* Verified Software Toolchain [https://vst.cs.princeton.edu/]&lt;br /&gt;
&lt;br /&gt;
* FSCQ [http://css.csail.mit.edu/fscq/]&lt;br /&gt;
&lt;br /&gt;
* Bedrock [http://plv.csail.mit.edu/bedrock/]&lt;br /&gt;
&lt;br /&gt;
* CFML [https://www.chargueraud.org/softs/cfml/]&lt;/div&gt;</summary>
		<author><name>Dornel</name></author>
	</entry>
</feed>