• Aucun résultat trouvé

Polynˆ omes pleins et polynˆ omes creu

Fonctionnelles et polymorphisme

6.1 Polynˆ omes pleins et polynˆ omes creu

Nous avons vu par deux fois des calculs sur les polynˆomes, d’abord repr´esent´es par des tableaux dans le chapitre 3 (section 3.3), puis comme des listes dans le chapitre 5 (section 5.5). Nous avons appel´e les seconds polynˆomes creux, les premiers polynˆomes pleins. Maintenant se pose ´evidemment le probl`eme de travailler avec ces deux repr´esentations en mˆeme temps, pour b´en´eficier des avantages de chacune d’elles : lorsqu’un polynˆome est plein, la repr´esentation `a l’aide d’un tableau est ´economique, car les degr´es sont implicites ; en revanche, lorsqu’un polynˆome est creux (comporte beaucoup de coefficients nuls), la repr´esentation en liste est pr´ef´erable — quand elle n’est pas tout simplement la seule envisageable, comme pour le polynˆome 1 + x1000000.

Nous aimerions donc repr´esenter les polynˆomes par un tableau ou une liste selon le cas, mais d´efinir des op´erations qui travaillent indiff´eremment sur l’une ou l’autre des repr´esentations. Or, ces deux types de repr´esentations sont incompatibles au point de vue du typage. Consid´erons la proc´edure d’impression des polynˆomes : nous avons d´efini deux fonctions, sp´ecifiques `a chacune des repr´esentations, imprime_polyn^ome_plein : int vect -> unit, qui imprime les polynˆomes pleins, et imprime_polyn^ome_creux : (int * int) list -> unit, qui imprime les polynˆomes creux. Pour avoir une primitive d’impression travaillant sur tous les polynˆomes, on aurait donc envie d’´ecrire :

let imprime_polyn^ome p = if p «est un polynˆome plein»

else imprime_polyn^ome_creux p;;

C’est effectivement la bonne id´ee, mais il faut la raffiner un peu : outre qu’on ne voit pas comment impl´ementer le pr´edicat « est un polynˆome plein », il se pose ´egalement un probl`eme de typage pour l’argument p de imprime_polyn^ome: est-ce une liste comme le sugg`ere l’appel de fonction imprime_polyn^ome_creux p, ou un tableau pour pouvoir ˆetre pass´e en argument `a imprime_polyn^ome_plein? On obtiendrait forc´ement une erreur de typage. Par exemple, en supposant que « est un polynˆome plein » renvoie toujours la valeur true :

# let imprime_polyn^ome p =

if true then imprime_polyn^ome_plein p else imprime_polyn^ome_creux p;; Entr´ee interactive:

> else imprime_polyn^ome_creux p;;

> ^

Cette expression est de type int vect,

mais est utilis´ee avec le type (int * int) list.

Il faut donc m´elanger les polynˆomes creux et pleins au sein d’un mˆeme type qui les comprenne tous les deux.

Le type polyn^ome

On d´efinit donc un nouveau type, polyn^ome, qui ´etablit explicitement le m´elange : il indique qu’il comprend deux cas possibles, le cas des polynˆomes pleins qui seront des tableaux d’entiers et le cas des polynˆomes creux qui seront des listes de paires d’entiers.

# type polyn^ome =

| Plein of int vect

| Creux of (int * int) list;; Le type polyn^ome est d´efini.

Le mot-cl´e type introduit la d´efinition du nouveau type polyn^ome. Apr`es le signe =, on ´ecrit la liste des possibilit´es du type en cours de d´efinition. Les noms Plein et Creux sont appel´es les constructeurs de valeurs du type (s’il n’y a pas d’ambigu¨ıt´e on dit simplement « constructeurs »). Comme d’habitude, la barre verticale | indique l’alternative et se lit « ou ». Le mot-cl´e of indique le type de l’argument du constructeur. Le type polyn^omecomprenant les valeurs d’un type plus les valeurs d’un autre type, on dit que c’est un type somme. On peut maintenant cr´eer des valeurs de type polyn^ome en appliquant l’un des deux constructeurs du type polyn^ome `a une valeur du type correspondant. Par exemple :

# let p1 = Plein [|1; 2; 3|];; p1 : polyn^ome = Plein [|1; 2; 3|] # let p2 = Creux [(1, 0); (1, 100)];; p2 : polyn^ome = Creux [1, 0; 1, 100]

Maintenant p1 et p2 sont du mˆeme type et pourront ˆetre arguments d’une mˆeme fonction.

Polynˆomes pleins et polynˆomes creux 111 Le filtrage est ´etendu `a tous les types somme et permet, ´etant donn´ee une valeur du type somme, de d´eterminer dans quel cas se trouve cette valeur. Pour le type polyn^ome, le filtrage va donc nous permettre d’impl´ementer la fonction « est un polynˆome plein » :

# let est_un_polyn^ome_plein = function | Plein _ -> true

| Creux _ -> false;;

est_un_polyn^ome_plein : polyn^ome -> bool = <fun>

Une fonction travaillant sur des valeurs de type polyn^ome fera typiquement une dis- crimination sur les valeurs du type par un filtrage du genre :

let f = function | Plein v -> ... | Creux l -> ...;;

Remarquez que le filtrage permet `a la fois de d´eterminer le type du polynˆome et de r´ecup´erer son tableau ou sa liste de monˆomes. C’est strictement analogue au cas des listes o`u nous ´ecrivions :

let f = function | [] -> ...

| x :: reste -> ...;;

C’est maintenant un jeu d’enfant que d’´ecrire la fonction d’impression des valeurs de type polyn^ome:

# let imprime_polyn^ome = function

| Plein v -> imprime_polyn^ome_plein v | Creux l -> imprime_polyn^ome_creux l;; imprime_polyn^ome : polyn^ome -> unit = <fun> # imprime_polyn^ome p1;;

1 + 2x + 3x^2- : unit = () # imprime_polyn^ome p2;; 1 + x^100- : unit = ()

Op´erations sur les valeurs de type polyn^ome

Nous d´efinissons l’addition et la multiplication des polynˆomes creux ou pleins. Puisque les polynˆomes se pr´esentent sous deux formes, nous avons quatre cas `a en- visager. L’id´ee est simple :

• la somme de deux polynˆomes creux est un polynˆome creux : on appelle l’addition des polynˆomes creux ;

• la somme de deux polynˆomes pleins est un polynˆome plein : on appelle l’addition des polynˆomes pleins ;

• la somme de deux polynˆomes d’esp`eces diff´erentes est un polynˆome creux. En effet, si l’un des polynˆomes est creux il comprend beaucoup de z´eros et sa somme avec un autre polynˆome comprendra aussi beaucoup de z´eros en g´en´eral (consid´erez par exemple (1 + x + 3x2) + (1 + x100)). Donc, dans le cas mixte, nous appelons encore l’addition des polynˆomes creux. Puisque l’un des polynˆomes est plein, nous avons be- soin d’une fonction qui transforme un polynˆome plein en polynˆome creux. C’est sans

difficult´e : nous parcourons le tableau des coefficients en accumulant dans une liste les monˆomes rencontr´es. La seule subtilit´e est de parcourir le tableau `a l’envers pour que le dernier monˆome ajout´e `a la liste soit bien celui de degr´e 0.

# let plein_vers_creux v = let l = ref [] in

for i = vect_length v - 1 downto 0 do if v.(i) <> 0 then l := (v.(i), i) :: !l done;

!l;;

plein_vers_creux : int vect -> (int * int) list = <fun>

L’addition des polynˆomes se d´efinit alors tr`es simplement :

# let ajoute_polyn^omes p1 p2 = match p1, p2 with

| Plein v, Plein v’ -> Plein (ajoute_polyn^omes_pleins v v’) | Creux l, Creux l’ -> Creux (ajoute_polyn^omes_creux l l’) | Plein v, Creux l ->

Creux (ajoute_polyn^omes_creux (plein_vers_creux v) l) | Creux l, Plein v ->

Creux (ajoute_polyn^omes_creux (plein_vers_creux v) l);; ajoute_polyn^omes : polyn^ome -> polyn^ome -> polyn^ome = <fun>

Ce code peut ˆetre l´eg`erement simplifi´e en remarquant que les deux derniers cas du filtrage sont presque identiques (ces deux cas se traduisent par deux clauses du filtrage dont la partie expression est la mˆeme). Pour ´eviter cette redite, on joue sur le fait que l’addition des polynˆomes est commutative pour traiter le dernier cas par un appel r´ecursif `a la fonction ajoute_polyn^omequi inverse les arguments p1 et p2.

# let rec ajoute_polyn^omes p1 p2 = match p1, p2 with

| Plein v, Plein v’ -> Plein (ajoute_polyn^omes_pleins v v’) | Creux l, Creux l’ -> Creux (ajoute_polyn^omes_creux l l’) | Plein v, Creux l ->

Creux (ajoute_polyn^omes_creux (plein_vers_creux v) l) | Creux l, Plein v ->

ajoute_polyn^omes p2 p1;;

ajoute_polyn^omes : polyn^ome -> polyn^ome -> polyn^ome = <fun>

Cette derni`ere solution permet de ne pas dupliquer de code, ce qui raccourcit l´eg`erement le texte de la fonction et diminue la probabilit´e d’introduire une erreur en ne modifi- ant qu’une des clauses lors de corrections ult´erieures du programme. En fait, lorsque l’expression `a renvoyer est compliqu´ee, l’appel r´ecursif s’impose sans contestation pos- sible. Cependant, cette solution pr´esente l’inconv´enient de sugg´erer que la fonction ajoute_polyn^ome est vraiment r´ecursive, alors qu’elle ne l’est que pour des raisons

«administratives ».

La multiplication n’est pas plus compliqu´ee :

# let rec multiplie_polyn^omes p1 p2 = match p1, p2 with

| Plein v, Plein v’ -> Plein (multiplie_polyn^omes_pleins v v’) | Creux l, Creux l’ -> Creux (multiplie_polyn^omes_creux l l’) | Plein v, Creux l ->

Types sommes ´elabor´es 113

Creux (multiplie_polyn^omes_creux (plein_vers_creux v) l) | Creux l, Plein v ->

multiplie_polyn^omes p2 p1;;

multiplie_polyn^omes : polyn^ome -> polyn^ome -> polyn^ome = <fun> # imprime_polyn^ome (multiplie_polyn^omes p1 p2);;

1 + 2x + 3x^2 + x^100 + 2x^101 + 3x^102- : unit = () # let p10000 = Creux [(1, 0); (1, 10000)];;

p10000 : polyn^ome = Creux [1, 0; 1, 10000]

# imprime_polyn^ome (multiplie_polyn^omes p10000 p10000);; 1 + 2x^10000 + x^20000- : unit = ()