Programmation objet et géométrie/Programmation avec DrGeoII
Bien que Dr. Geo soit un logiciel de géométrie dynamique, le seul fait qu'il soit muni d'une console Smalltalk permet d'envisager son utilisation pour l'enseignement de l'algorithmique, dans le cadre de TP de programmation. Le principe de fonctionnement de la console est basée sur les éléments suivants:
- Le script Smalltalk qu'on a tapé dans la console fait partie intégrante de la figure, et est sauvegardé avec celle-ci.
- Le script est un objet abstrait, qui doit être muni d'un costume: Son apparence; celui-ci est la valeur retournée, et s'affiche dans la figure, à l'endroit où l'utilisateur a cliqué avec la souris.
- Chaque objet est inscrit dans son propre script, ce qui peut mener à une impressionnante collection de scripts dans une figure.
Pour rester conforme au programme d'algorithmique du Lycée, on insistera ici sur les aspects non objets de Smalltalk.
Affectations successives
modifierOn va examiner ici une suite d'affectations, résumée par
- On met 3 dans x;
- On incrémente x;
- Puis on élève x au carré.
La question étant bien entendu quelle est la valeur finale de x?
Sorties de données
modifierPour commencer, on va tout faire en interne, avec la déclaration préalable des données (ici seulement x) dont le programme a besoin, et les instructions d'affectation (notées par un double-point-égal), chaque instruction étant finie par un point:
EntreeInterne
|x|
x:=3.
x:=x+1.
x:=x*x.
^x
Comme il faut bien que le programme produise quelque chose, ce sera la valeur finale de x, que le script va sortir de son chapeau comme le ferait un magicien, et que l'on représente donc par un chapeau. Si on entre au clavier Control+S pour sauvegarder le script, l'absence de message d'erreur indique que le script est correct du point de vue de la syntaxe Smalltalk. Mais pour en voir l'effet, il faut le lancer, ce qu'on fait depuis le menu de Dr. Geo (dernier item, celui avec les objets numériques). Là il suffit de cliquer n'importe où sur la figure pour y trouver l'affichage d'un 16.0 qui donne la réponse à la question.
Entrées de données
modifierPour entrer une donnée numérique dans un script, on va définir celui-ci comme dépendant d'une donnée (qui devra être numérique parce que des calculs seront effectués dessus). Lors de l'exécution du script, on va cliquer sur le nombre. Ce qui nécessite de placer un nombre dans la figure, puis d'entrer le script suivant:
Entree: xInit
|x|
x:=xInit valueItem.
x:=x+1.
x:=x*x.
^x
Le paramètre dont ce script dépend a été appelé xInit (abréviation pour la valeur initiale de x) pour le distinguer de la variable x, qu'on affecte d'abord avec xInit valueItem: xInit est en quelque sorte converti en nombre, ou plus précisément c'est sa valeur qui est récupérée. Le reste est fait comme précédemment.
L'intérêt de cette façon de faire est que le nombre 3 sur lequel on a cliqué pour voir 16 peut très bien être mobile, comme par exemple l'abscisse d'un point sur un segment, ce qui introduit la notion de nombre dépendant d'un autre nombre, et donc de fonction.
Mode pas-à-pas
modifierPour comprendre pourquoi la valeur finale est 16, il faut un moyen d'afficher ligne après ligne le contenu de x. Pour cela, Dr. Geo a un débogueur intégré, mais a priori inaccessible: Où dont-on cliquer pour invoquer celui-ci?
Débogueur
modifierL'usage en Smalltalk est de créer une erreur pour que le débogueur vienne tout seul à la rescousse: Le meilleur moyen de voir un Saint-Bernard, c'est de déclencher une avalanche! En fait c'est très ergonomique, les bogues ayant tendance à survenir très souvent! Pour faire apparaître le débogueur on va donc fermer la figure DrGeo ce qui fait apparaître une sorte de bureau sur lequel était posée la figure, et là, avec Control+K (sous windows c'est Alt+K), on crée (instancie!) un espace de travail Smalltalk, qui ressemble à un éditeur de texte. Là on recopie le script précédent avec une nouvelle variable y que l'on va traîtreusement diviser par 0 à la fin (certes çe serait plus propre de remplacer la dernière ligne par self halt. qui là aussi arrêterait la machine Smalltalk en ouvrant le débogueur):
|x y|
x:=3.
x:=x+1.
x:=x*x.
y:=1/0.
Ensuite, on essaye d'exécuter ce script erroné, en le sélectionnant (par exemple avec Control+A) puis en effectuant Control+D qui appelle la méthode do it!, et on voit un message d'erreur avec une invitation à faire sortir le génie de la bouteille, le génie ici étant bien entendu le débogueur. En cliquant sur Restart on arrive au début du script:
En cliquant sur la lettre x dans la liste des variables (troisième cadre en bas, on ne la voit pas ci-dessus parce qu'elle est placée après thisContext, stack top et all temp vars) on constate (en bas à droite) que pour l'instant x contient nil (il n'y a rien dedans). Pour avoir un effet, on va cliquer sur Over ce qui va passer à la ligne suivante:
Initialement
modifierUne fois qu'on a mis 3 dans x, x contient 3:
Un deuxième clic sur Over sélectionne le x+1 dans la ligne 3, ce qui, par comparaison avec le 3 que contient x, permet de deviner le prochain contenu de x, qu'on vérifie en cliquant à nouveau sur Over:
Ensuite
modifierQuand on ajoute 1 à quelque chose qui contenait 3, ce quelque chose contient 4:
Fin
modifierLe dernier clic sur Over donne la valeur finale de x:
Non seulement on sait que à la fin, x contient 16, mais on sait pourquoi:
- Au début x contenait 3 parce qu'on a mis 3 dans x.
- Ensuite x contenait 4 parce qu'on a ajouté 1 à son ancien contenu 3, et que 3+1=4;
- Enfin x contient 16 parce qu'on a élevé son ancien contenu 4 au carré.
Représentation graphique d'une fonction
modifierFonction
modifierOn va regarder comment Dr. Geo peut représenter graphiquement la fonction . Pour éviter d'avoir à écrire trop souvent cette expression, on va définir un objet Smalltalk appelé fonction qu'on pourra donc appeler par la suite:
fonction := [:x | x*x*x/25-x].
Représentation graphique point par point
modifierOn crée une boucle sur x dans laquelle on va placer (créer) le point de coordonnées . Pour cela, on va automatiser complètement la création de la représentation graphique, et créer la figure Dr. Geo avec, elle s'appellera figure. Ceci ne se fait pas dans une figure existante (puisqu'on veut la créer!) mais sur le bureau de Squeak. Une fois ce bureau vide, on entre Control+K pour ouvrir un éditeur de texte (worKplace). Après avoir défini la fonction comme ci-dessus, on crée une figure Dr. Geo avec la méthode new de DrGeoCanvas. Puis dans une boucle allant de -5 à 5 par pas de 0,4 et dont l'indice s'appelle x, on crée le point p de coordonnées x et la valeur que prend fonction en x:
| figure fonction p|
fonction := [:x | x*x*x/25-x].
figure := DrGeoCanvas new.
-5 to: 5 by: 0.4 do:
[:x |p:=figure point: x@(fonction value: x)].
Après avoir sélectionné le texte et entré Control+D, on voit la figure avec des points en forme de croix, qui suggèrent en pointillés la représentation graphique de la fonction. Pour améliorer la représentation, on peut mettre les points en plus petit et les choisir plus nombreux:
| figure fonction p|
fonction := [:x | x*x*x/25-x].
figure := DrGeoCanvas new.
-5 to: 5 by: 0.1 do:
[:x |
p:=figure point: x@(fonction value: x).
p round.
p small.].
Mais la représentation graphique est toujours en pointillés, et il vaut mieux représenter la courbe en l'approchant par un polygone, soit par des segments:
Représentation polygonale
modifierAvec des segments
modifierOn peut tout simplement modifier le script précédent pour qu'il joigne le point p au point q qui le suit (et qu'on doit donc rajouter dans le script), sans oublier toutefois de remplacer à chaque pas de la boucle, p par q (et en cachant les deux points):
| figure fonction p q s|
fonction := [:x | x*x*x/25-x].
figure := DrGeoCanvas new.
p:=figure point: -5@(fonction value: -5).
p hide.
-4.9 to: 5 by: 0.4 do:
[:x |
q:=figure point: x@(fonction value: x).
q hide.
s:=figure segment: p to: q.
s color: Color blue.
p:=q.].
On remarque la logique de Smalltalk, pour qui un segment va d'un point à un autre, et n'est pas défini à partir de ses extrémités. Le segment s'appelle s ce qui permet de le colorier en bleu. On remarque aussi que pour cacher un point p, on lui demande de se cacher, en lui envoyant un message hide.
Avec un polygone
modifierSi on rajoute les deux projetés orthogonaux des extrémités du polygone précédent sur l'axe des abscisses, on peut envisager de fermer le polygone. Pour l'exemple qui suit, on va prendre une fonction positive, c'est-à-dire que sa représentation graphique est entièrement au-dessus de l'axe des abscisses. Ceci pour éviter que le polygone soit croisé, et pour une autre raison qui apparaîtra dans l'exemple. On va donc considérer un nouvel objet, une liste de sommets. C'est pour Smalltalk une OrderedCollection, qui est un tableau (voir ci-dessous le lancer de dés pour un exemple de tableau) dont le taille n'est pas fixée d'avance, afin de pouvoir y ajouter au fur et à mesure les sommets du polygone. La création du polygone se fera à partir de cette liste de sommets.
La liste de sommets, initialement vide, se crée par
sommets:=OrderedCollection new.
Pour ajouter un sommet (la référence d'un point nouvellement créé, mais caché) on envoie à sommets le message add suivi du nom du sommet à insérer dans la liste. Comme on choisit l'intervalle [a;b]=[-1;1], on commence donc par le point de coordonnées (a;0):
figure:=DrGeoCanvas new.
a:=-1.
b:=1.
sommets add: a@0.
Ensuite on ajoute les points de la courbe:
a to: b by: 0.1 do: [ :x | sommets add: x@(fonction value: x). ].
Enfin, le point de coordonnées (b;0):
sommets add: b@0.
Le polygone construit avec la liste de sommets ainsi constituée s'appelle integrale. En effet il est par défaut, rempli (ici en bleu) et son aire est :
integrale:=figure polygon: sommets.
integrale color: Color blue.
Voici le script intégral de l'intégrale:
|figure fonction integrale sommets a b|
fonction:=[ :x | x*x ].
sommets:=OrderedCollection new.
figure:=DrGeoCanvas new.
a:=-1.
b:=1.
sommets add: a@0.
a to: b by: 0.1 do: [ :x | sommets add: x@(fonction value: x). ].
sommets add: b@0.
integrale:=figure polygon: sommets.
integrale color: Color blue.
Représentation comme un lieu
modifierPoint mobile
modifierVariante plus légère: Avec un seul point, mais qu'on promène de telle manière que quelle que soit son abscisse, son ordonnée reste égale à l'image de celle-ci par la fonction. Tout d'abord, on va ralentir le mouvement du point-escargot pour avoir le temps de le voir bouger:
On va rajouter à la figure précédente une variable pause qui est un objet Delay. On place p au départ de son trajet, sur son starting-block, et on règle la pause sur 0,2 secondes, une bonne vitesse d'escargot. Ensuite au lieu de créer d'autres points, on va déplacer celui qu'on a et lui faire parcourir la courbe, en rafraichissant l'affichage au fur et à mesure:
|figure fonction p pause|
fonction := [:x | x*x*x/25-x].
figure := DrGeoCanvas new.
p:=figure point: -5@0.
pause := Delay forSeconds: 0.2.
[-5 to: 5 by: 0.1 do:
[:x |
p mathItem moveAt: x@(fonction value: x).
p name: (fonction value: x) asString.
figure domain updateAllMathItems.
pause wait.].
] fork
Trace du point
modifierOn peut maintenant imaginer que l'escargot dépose de la bave au cours de son trajet, et définir la représentation graphique comme la trace laissée par la bave: Un lieu géométrique.
On va alors un segment s d'extrémités (-5;0) et (5;0), attacher un point mobile à ce segment, construire un point courbe sur la courbe, de coordonnées (x;y) où x est l'abscisse de mobile puis construire le lieu de courbe quand mobile bouge. Mais pour construire le point courbe, on doit définir ses coordonnées par un bloc de Smalltalk, c'est-à-dire une fonction qui récupère l'abscisse du point par mathItem point x et calcule l'image de l'abscisse par la fonction:
|figure s mobile courbe bloc|
figure := DrGeoCanvas new.
s:=figure segment: -5@0 to: 5@0.
mobile := figure pointOnCurve: s at: 0.1.
bloc := [:mathItem| |x|
x := mathItem point x.
x @ (x * x * x / 25 - x)].
courbe := figure point: bloc parent: mobile.
figure locusOf: courbe when: mobile.
Cette méthode consistant à calculer les coordonnées d'un point par bloc permet aussi de représenter des courbes paramétrées, ou des images de droites et cercles par des transformations complexes...
Probabilités
modifierSimulation du hasard
modifierPour avoir un nombre aléatoire entier, par exemple entre 1 et 6 comme avec un dé, on peut faire
de:=[(1 to: 6) atRandom] value.
On choisit au hasard un élément de la liste des entiers de 1 à 6 et on demande sa valeur (sinon on n'aurait pas un nombre). Cette méthode est très puissante, permettant par exemple de choisir un petit nombre premier au hasard avec
de:=#(2 3 5 7 11 13 17) atRandom.
mieux encore, si on ne se souvient pas des nombres premiers, on peut les faire tester par Smalltalk:
gener:=[((2 to: 100) select: [:n | n isPrime]) atRandom].
de:=gener value.
(parmi les nombres de 2 à 100, on sélectionne les n tels que n est premier; on fabrique avec eux une liste au sein de laquelle on peut choisir un nombre au hasard)
Lancer de deux dés
modifierPour lancer deux dés, il suffit de faire la même chose que ci-dessus, mais en double, et d'additionner les résultats. Si de1 et de2 sont les résultats des lancers des deux dés, alors leur somme est comprise entre 2 et 12, et on peut faire une statistique sur celle-ci pour voir si par exemple, le 3 sort aussi souvent que le 7. La complexité supplémentaire vient de l'utilisation d'un tableau pour les effectifs. On le crée par
stats:=Array new: 12.
Le tableau est alors vide, mais il contient 12 places où placer les effectifs. Pour que Dr. Geo sache que ce sont des entiers, on va les initialiser à 0:
stats:=Array new: 12.
2 to: 12 do: [:i | stats at: i put: 0].
À ce stade, comme on n'a pas mis de 0 dans le premier emplacement du tableau (la somme des résultats des deux dés ne peut jamais être 1), il contient ceci:
(nil 0 0 0 0 0 0 0 0 0 0 0)
Le nil sur la première entrée signifiant qu'elle est vide, les autres entrées (de 2 à 12) sont actuellement nulles (puisqu'on n'a pas encore lancé les dés).
Ensuite on lance les dés: Pour chaque valeur de l'indice i de la boucle, on additionne (après les avoir calculés) les nombres de1 et de2, puis on ajoute 1 à l'entrée du tableau indexée par la somme:
stats:=Array new: 12.
2 to: 12 do: [:i | stats at: i put: 0].
gobelet:=[(1 to: 6) atRandom].
1 to: 100 do: [:i|
de1:=gobelet value.
de2:=gobelet value.
s:=de1+de2.
stats at: s put: ((stats at: s)+1).
].
Un résultat typique est celui-ci:
(nil 4 4 5 15 14 18 11 13 5 7 4)
Il est clair que le 7, qui est sorti 18 fois, est plus fréquent que le 3, qui n'est sorti que 4 fois.
Pour avoir un diagramme en bâtons, on peut dessiner les segments à partir des éléments du tableau (abscisses: les indices; longueurs: les affectifs, divisés par 100 pour ne pas avoir de dessin trop allongé). Pour effectuer 10 000 lancers, on peut faire ainsi:
|figure gobelet de1 de2 s stats a b|
stats:=Array new: 12.
2 to: 12 do: [:i | stats at: i put: 0].
gobelet:=[(1 to: 6) atRandom].
figure := DrGeoCanvas new.
1 to: 10000 do: [:i|
de1:=gobelet value.
de2:=gobelet value.
s:=de1+de2.
stats at: s put: ((stats at: s)+1).
].
2 to: 12 do: [:i|
s:=figure segment: i@0 to: i@((stats at: i) /100).
s color: Color red.
].
Il faut moins d'une seconde pour créer le graphique.
Et une version légèrement différente :
|figure gobelet stats item|
stats:=Array new: 12 withAll: 0.
gobelet:=[(1 to: 6) atRandom].
figure := DrGeoCanvas new.
1 to: 10000 do: [:i | | somme |
somme := gobelet value + gobelet value.
stats at: somme put: ((stats at: somme) + 1)].
2 to: 12 do: [:i|
item :=figure segment: i@0 to: i@((stats at: i) / 100).
item color: Color red.
item := figure point: i@0.
item show; square; color: Color blue; name: i asString].
Suites
modifierLa suite logistique définie par est chaotique. Pour le vérifier, on peut la représenter graphiquement avec Dr. Geo :
|figure s r u|
figure:=DrGeoCanvas new.
s:=figure segment: 0@(-1) to: 4@(-1).
r:=figure pointOnCurve: s at: 0.8.
s:=figure segment: 0@0 to: 0@1.
u:=figure pointOnCurve: s at: 0.7.
u round small.
u color: Color blue.
1 to: 100 do: [:n|
u:=figure
point: [:parents| |y t|
y:=parents first point y.
t:=parents second point x.
(n/5)@(t*y*(1-y))]
parents: {u.r}.
u round small.
u color: Color blue].
La manipulation des curseurs montre dynamiquement le phénomène de dédoublement de période. En simplifiant légèrement le script, on peut vérifier l'effet de la raison d'une suite géométrique sur sa convergence.
On peut aussi utiliser une réglette de valeur pour simplifier le code source :
| figure r u |
figure:=DrGeoCanvas new.
r := figure float: 0.8 at: 0@ -1 from: 0 to: 4 name: 'r' showValue: true.
u := figure pointOnCurve: (figure segment: 0@0 to: 0@1) at: 0.7.
u round small color: Color blue.
1 to: 1000 do: [:n|
u := figure
point: [:parent| | y |
y := parent point y.
(n / 5) @ (r value * y * (1 - y))]
parent: u.
u round small color: (n even ifTrue: [Color blue] ifFalse: [Color red])].