Pile

publicité
Programmation Fonctionnelle I, 2009-2010
L1 Info&Math - Faculté des Sciences de Nice
Cours n°11
http://deptinfo.unice.fr/~roy
Les Arbres Binaires
d'Expressions (2)
Parcours Itératif
Calcul Formel
Le type abstrait PILE
empiler
dépiler
• Les piles forment un type de
donnée fonctionnant en ordre
LIFO [Last In, First Out].
sommet
• Nos piles seront fonctionnelles.
La fonction (empiler x P) ne
modifie pas la pile P, elle se
contente de calculer une
nouvelle pile ! Toutes les
opérations sont en O(1).
>
>
3
>
4
>
3
(pile-vide)
(pile-vide? P)
; constructeur de pile vide
; Pile → Boolean
(empiler x P)
; Elément × Pile → Pile*
(depiler P)
; Pile* → Pile
(sommet P)
; Pile* → Elément
où Pile* dénote l'ensemble des piles non vides.
(define P (empiler 3 (empiler 4 (empiler 5 (pile-vide)))))
(sommet P)
(sommet (depiler P))
(sommet P)
3
4
5
• L'implémentation du type abstrait passe ici aussi par les listes, qui
sont bien pratiques !
(define (pile-vide)
empty)
(define (pile-vide? pile)
(empty? pile))
Pile → Boolean
∅ → Boolean
(define (empiler x pile)
(cons x pile))
Elément × Pile → Pile*
(define (sommet pile)
(if (empty? pile)
(error 'sommet "Pile vide !")
(first pile)))
Pile* → Elément
(define (depiler pile)
(if (empty? pile)
(error 'depiler "Pile vide !")
(rest pile)))
Pile* → Pile
N.B. Les fichiers adt-arbre.scm
et adt-pile.scm sont rédigés
sous la forme de modules [horsprogramme], avec une syntaxe
un peu particulière...
A quoi sert une PILE ?
• En électricité, à fournir de l'énergie. En cuisine, à stoker des
assiettes. En programmation, à stocker des résultats intermédiaires
qu'il faudra utiliser plus tard !
• Par exemple, pour exécuter une récurrence enveloppée, une pile
implicite [non visible au programmeur] est utilisée par le système.
(fac n) = (* (fac (- n 1)) n)
Pour calculer n! , je calcule (n-1)! mais auparavant je mets la
multiplication par n en attente dans une pile...
calculer 3!
calculer 2!
calculer 1!
calculer 0!
* 1
* 3
6
2
* 2
* 2
* 3
* 3
1
1
• Les ordinateurs sont des machines à pile. Lorsqu'un logiciel fonctionne
sur votre machine, une pile invisible et silencieuse est utilisée...
• Le programmeur lui-même peut avoir besoin de modéliser une pile dans
le programme qu'il rédige. Reprenons la calculette HP [cours 10 p. 14]
> (scheme->hp '(- (* 2 (+ 5 1)) (/ 4 2)))
(2 enter 5 enter 1 enter + * 4 enter 2 enter / -)
• Nous savons traduire une expression Scheme [arbre] en une séquence
de touches pour HP. Maintenant nous aimerions jouer itérativement
cette séquence de touches ! Pour cela, il nous faut une pile explicite :
2 enter
5 enter
2
1 enter
+
*
5
5
6
2
2
2
/
10
1
2
2
4
12
12
2 enter
12
4 enter
4
12
(define (exec-HP codeHP)
; on suppose codeHP syntaxiquement correct !
(local [(define (iter L pile)
(begin (printf "L=~a pile=~a\n" L pile)
; pour le debug...
(cond ((empty? L) (sommet pile))
((number? (first L))
(iter (rest (rest L)) (empiler (first L) pile)))
(else (local [....]
(iter (rest L) ...))))))]
cf TP!
(iter codeHP (pile-vide))))
• Exemple :
> (scheme->hp '(- (* 2 (+ 5 1)) (/ 4 2)))
(2 enter 5 enter 1 enter + * 4 enter 2 enter / -)
> (exec-HP '(2 enter 5 enter 1 enter + * 4 enter 2 enter / -))
L=(2 enter 5 enter 1 enter + * 4 enter 2 enter / -) pile=()
L=(5 enter 1 enter + * 4 enter 2 enter / -) pile=(2)
L=(1 enter + * 4 enter 2 enter / -) pile=(5 2)
L=(+ * 4 enter 2 enter / -) pile=(1 5 2)
L=(* 4 enter 2 enter / -) pile=(6 2)
L=(4 enter 2 enter / -) pile=(12)
L=(2 enter / -) pile=(4 12)
L=(/ -) pile=(2 4 12)
L=(-) pile=(2 12)
L=() pile=(10)
10
Une application des piles : parcours itératif d'un arbre
• Les arbres binaires formant un type de donnée récursif, il est naturel
de les programmer récursivement !
• Mais il peut être intéressant de produire un algorithme itératif.
• La présence de deux fils introduit des mises en attente. Munissonsnous donc d'une pile en paramètre et empilons-y les sous-arbres qu'il
restera à visiter !
• Exemple : soit à calculer le nombre de feuilles d'un arbre A.
STRATEGIE DU PARCOURS EN PROFONDEUR ITERATIF :
- si je suis sur un noeud, j'empile [un pointeur sur] le fils droit
et je vais visiter le fils gauche.
- si je suis sur une feuille, je regarde s'il reste un arbre à visiter
au sommet de la pile.
(define (nb-feuilles A)
;
le nombre de feuilles
arbre, pile courante, résultat courants
(local [(define (iter A P acc) ;
(begin (printf "A=~a P=~a acc=~a\n" A P acc)
; pour le debug...
(if (feuille? A)
(if (pile-vide? P)
(+ acc 1)
(iter (sommet P) (depiler P) (+ acc 1)))
(iter (fg A) (empiler (fd A) P) acc))))]
(iter A (pile-vide) 0)))
> (nb-feuilles '(- (* 2 (+ x 1)) (/ x y)))
A=(- (* 2 (+ x 1)) (/ x y)) P=() acc=0
A=(* 2 (+ x 1)) P=((/ x y)) acc=0
-
A=2 P=((+ x 1) (/ x y)) acc=0
A=(+ x 1) P=((/ x y)) acc=1
/
*
A=x P=(1 (/ x y)) acc=1
A=1 P=((/ x y)) acc=2
A=(/ x y) P=() acc=3
A=x P=(y) acc=3
2
x
+
A=y P=() acc=4
==> 5
x
1
y
• Exemple d'analyse syntaxique itérative : reconstruction d'un arbre à
partir de son parcours postfixe [cf Cours 10 pp. 17-20].
On analyse itérativement le
parcours postfixe :
(2 3 5 / 8 * +)
+
*
cf TP !
/
2
et on reconstruit l'arbre d'origine :
• Vous visualisez la pile ?...
3
5
8
(+ 2 (* (/ 3 5) 8))
Introduction au
Calcul Formel
Maxima
Qu'est-ce que le CALCUL FORMEL ?
• Le calcul formel [computer algebra] consiste à pratiquer une
algorithmique symbolique sur des objets mathématiques.
• Symbolique est apposée à numérique : on travaille sur des formes
exactes plutôt que sur des approximations numériques.
f'(2) → 1.6756288108066286
f(x) = 2 cos(3x)
calcul numérique
f'(x) → -6 sin(3x)
calcul symbolique ou formel
• Les premiers gros systèmes de calcul formel furent REDUCE [1968]
et MACSYMA [1970], tous deux écrits en LISP. MACSYMA existe
encore, on peut le télécharger [http://maxima.sourceforge.net].
http://fr.wikipedia.org/wiki/Logiciel_de_calcul_formel
(logiciel
libre !)
Le Problème de la Simplification
• C'est en fait le problème central ! Comment réduire les expressions à
des formes irréductibles, de préférence canoniques [pour l'unicité] ?
• Un évaluateur peut calculer la valeur numérique d'un arbre :
(valeur '(+ (* 2 3) 5))
→ 11
(valeur '(+ (* x 2) y)) '((x 3) (y -1))) → 5
• Un simplificateur sait travailler avec des arbres algébriques, en
présence d'inconnues :
(simplif '(+ (* x (- 5 4)) (+ 1 (* 0 y)))) → (+ x 1)
• Il s'agit donc bien d'un algorithme formel, avec localement quelques
calculs sur constantes, comme celui de (- 5 4).
Algebraic simplification: a guide for the perplexed
Joel Moses, Project MAC, MIT, 1971
• Nous essayons de simplifier nos arbres binaires d'expressions. Pour
être modulaire, le toplevel du simplificateur se contente d'aiguiller sur
des simplificateurs spécialisés :
(define (simplif A)
(if (feuille? A)
A
(case (racine A)
((+) (simplif+ A))
((-) (simplif- A))
((*) (simplif* A))
((/) (simplif/ A))
(else (error 'simplif
(format "Opérateur inconnu ~a" (racine A)))))))
• Chaque spécialiste va implémenter ses propres règles de réduction :
(define (simplif+ A)
; on sait que A est un arbre de racine +
(local [(define Ag (simplif (fg A))) (define Ad (simplif (fd A)))]
(cond ((and (number? Ag) (number? Ad)) (+ Ag Ad))
((equal? 0 Ag) Ad)
((equal? 0 Ad) Ag)
(else (arbre '+ Ag Ad)))))
etc, cf TP !
Le Problème de la Dérivation Symbolique
A = y/x
→
∂
∂x
A = -y/x2
∂
∂y
A = 1/x
∂
∂z
A = 0
• Il s'agit de dériver un arbre d'expression A par-rapport à une
variable v. Ici aussi le toplevel du dérivateur va aiguiller sur des
dérivateurs spécialisés. Le résultat doit être simplifié autant que faire
se peut car la taille des arbres dérivés croît très vite !
(define (diff A v)
; retourne ∂A/∂v
(if (feuille? A)
(if (equal? A v) 1 0)
; ∂v/∂v=1 et ∂u/∂v=0
(simplif (case (racine A)
((+ -) (diff+- A v))
((*)
(diff* A v))
((/)
(diff/ A v))
(else (error 'diff (format "Op. inconnu" (racine A)))))))
• On se laisse guider par les règles de dérivation usuelles :
∓
A
B
'




A
∓



A'
B'
*
B
'


+
*
A' B


A
/
B
'


*
A B'
/
*
*
*
B
etc
B
A' B A B'
• Voici par exemple le spécialiste de la dérivation d'une somme :
(define (diff+- A v)
; A = ( Ag Ad)
(arbre (racine A) (diff (fg A) v) (diff (fd A) v)))
∓
• Notez la récurrence croisée : diff utilise diff+- et inversement !
Le Problème de l'Intégration Symbolique
• On parle bien de recherche d'une primitive, pas de calcul numérique !
• Hélas le problème est infiniment plus difficile ! Si la dérivation est
purement mécanique, l'intégration requiert de l'expertise...
• Stratégie ? Intégration par parties, changement de variables ?
• PROBLEME : certaines fonctions
n'ont PAS de primitive exprimable
comme combinaison de fonctions
élémentaires.
(algorithme de Risch, 1969)
• Les logiciels de calcul formel font
de leur mieux...
!
x2
e dx
Arbres et Fonctions
• Un arbre est une expression, pas une fonction ! Comme si l'on
2
2
confondait x + 1 et x !→ x + 1
• Comment construire la fonction associée à un arbre ?
- on part d'un arbre A, par exemple (* x (+ x 2)), en la variable x.
- on sait prendre sa valeur en un point x0 avec (valeur A AL)
- on sait construire une fonction avec le constructeur lambda.
(define (arbre->fonction A v)
; v est la variable
(lambda (x0) (valeur A (list (list v x0)))))
x, y, ...
> (define A '(* x (+ x 2)))
> (define f (arbre->fonction A 'x))
> (map (lambda (x) (list x (f x))) (list 0 1 2 3 4))
((0 0) (1 3) (2 8) (3 15) (4 24))
N.B. On a passé la variable de l'arbre en paramètre. On pourrait aussi la
trouver automatiquement (s'il n'y en a qu'une)...
Dessiner la courbe d'une fonction
• Bon, la fonction fonctionne. Si l'on veut en plus dessiner sa courbe, on
demande le module plot :
(require (lib "plot.ss" "plot"))
> (define df (arbre->fonction (diff A 'x) 'x))
> (plot (mix (line f (color 'blue) (width 2))
(line df (color 'red) (width 2)))
(x-min -4) (x-max 3) (y-min -3) (y-max 8))
;
la dérivée
Dem
sur ande
le m z de
ot p l'ai
lot. de
..
Téléchargement