|
Télécharger
le .dir correspondant à cette étape |
Maintenant que nous savons dédoublonner les mots qui composent le texte, et que nous savons afficher chaque mot différent par ordre alphabétique, accompagné de son nombre d'occurrences, nous allons introduire un nouveau perfectionnement. Donner le choix de les classer : - par ordre
alaphabétique ou ordre alphabétique inverse, Il faudra donc, lorsque l'analyse des occurrences sera affichée, pouvoir choisir entre un tri par ordre alphabétique et un tri par nombre d'occurrences. Pour cela, un bouton supplémentaire sera nécessaire pour passer de l'un à l'autre. Ce bouton supplémentaire fonctionnera également en bascule. Par ailleurs, quel que soit le critère de tri choisi, un dernier bouton, permettra d'inverser l'ordre du tri, croissant ou décroissant. Ce dernier bouton fonctionnera lui aussi en bascule. Ces deux boutons ne seront visibles et actifs que si la fenêtre de résultat montre l'analyse des occurrences. Si la fenêtre des résultats montre les statistiques, alors ces deux boutons supplémentaires seront invisibles, ainsi que leur légende. Quand elles sont visibles, les légendes de ces deux boutons devront changer en fonction de l'état de l'application. Enfin, nous faisons le choix suivant : quand on vient de l'affichage des statistiques pour passer en analyse des occurrences, le premier affichage sera une présentation triée par ordre alphabétique croissant d'occurrences. |
NB : on remarquera que, l'application ayant atteint une certaine complexité, les médias et les scripts ont été réorganisés (et renommés pour certains scripts), dans la distribution.
Il nous faut d'abord
compléter notre interface et y rajouter les deux boutons supplémentaires
et leur légende.
Le premier bouton, permettant de choisir le critère de tri affichera
donc comme légende "Trier par ordre d'occurrences" lorsque ce
sera l'ordre alphabétique qui sera affiché (toujours parce que
la légende indique ce qui se passera dans le futur si on clique sur le
bouton), et cette légende deviendra donc "Trier par ordre alphabétique"
lorsque sera affiché le tri par ordre d'occurrences.
En revanche, nous avons choisi de garder la légende du bouton d'ordre de tri constante, puisque son utilité est intuitive. Cette légende sera donc "Inverser l'ordre (croissant / décroissant)". Mais si nous voulions la changer malgré tout, alors ce serait exactement la même manipulation que pour les autres boutons.
Pour plus de clarté,
nous allons traiter cette étape en deux temps :
- nous implémenterons d'abord le changement de critère de tri,
- ensuite nous implémenterons
le changement d'ordre de tri.
Changement
du critère de tri :
Quelle sera l'architecture du script de notre bouton ?
La même que celle que nous rencontrons toujours :
- une ligne de déclaration de propriétés,
- un beginSprite pour s'occuper des initialisations,
- un mouseEnter et un mouseLeave pour la lisibilité
de l'interface (bouton en surbrillance si on le survole),
- puis enfin un mouseUp : la prise en charge de cet événement
est de toutes façons nécessaire puisque c'est un bouton qu'on
clique.
- enfin, nous avons le choix : écrire le traitement de retriage dans
une méthode à part qui serait appelée par le mouseUp,
ou écrire ce traitement directement dans le mouseUp.
Dans l'exercice, du fait que ce traitement n'a pas vocation à resservir
en dehors de l'utilisation de ce bouton, nous avons choisi de l'écrire
dans le corps du mouseUp. Mais l'autre option est tout aussi valable.
Du fait que notre
bouton est une bascule et affichera tantôt un tri par ordre alphabétique
ou un tri par nombre d'occurrences, nous retrouvons notre notion de mode qui
oscille entre deux états à chaque fois que l'on clique sur le
bouton (en plus du traitement).
Nous allons cette fois-ci modéliser ce mode à l'aide d'une syntaxe
et d'un type de données un peu différents de ceux que nous avons
jusque là utilisés : nous avons donné à l'indicateur
de mode de fonctionnement un nom un peu plus explicite, et nous l'avons appelé
"tri". Comme cet indicateur doit se souvenir
de sa valeur d'une utilisation du bouton à l'autre, tri sera
mis en propriété. D'autre part, au lieu de lui donner des valeurs
numériques, nous allons lui donner des valeurs également plus
évocatrices : "alphabetique" et "frequence".
On pourrait bien sûr se contenter de ces valeurs en tant que chaînes
de caractères, et tester le contenu de cette chaîne pour choisir
le traitement à appliquer. Mais le traitement des chaînes est long
(comparaison caractère par caractère). Les concepteurs ont donc
doté le langage d'un nouveau type de données, elle aussi appelée
propriété et elle se note à l'aide d'un #
placé en préfixe du nom de la propriété.
Le fait d'utiliser ce type de données optimise le code dans la mesure
où, au moment de la compilation, une telle propriété est
transformée en référence interne et que les opérations
qu'on peut faire dessus seront considérablement plus rapides. En revanche,
je ne suis pas sûr que les opérations sur ce type soit plus rapide
que sur des valeurs numériques (tri = soit 1 soit 2 par exemple).
Mais alors que les valeurs numériques ne sont que des conventions, qu'il
faut connaître pour interpréter le code, les propriétés
offrent l'avantage de se présenter sous un nom explicite (*).
Nous écrirons donc, sur la ligne de déclaration des propriétés
et dans le beginSprite, puis dans les gestionnaires d'événements
qui suivent :
property spriteNum, tri
on
beginSprite me
tri = #frequence
sprite(spriteNum + 1).member.text
= "Trier par ordre d'occurrences"
end
on
mouseEnter me
sprite(spriteNum).member
= "petitBAct"
end
on
mouseLeave me
sprite(spriteNum).member
= "petitB"
end
on
mouseUp
if tri = #alphabetique
then
![]()
sprite(spriteNum
+ 1).member.text = "Trier par ordre d'occurrences"
![]()
faire
un traitment de tri par ordre alphabétique et stocker le résultat
dans une variable leMessage
![]()
tri
= #frequence
else
if tri = #frequence then
![]()
sprite(spriteNum
+ 1).member.text = "Trier par ordre alphabétique"
![]()
faire
un traitment de tri par nombre d'occurrences et stocker le résultat dans
une variable leMessage
![]()
tri
= #alphabetique
end
if
afficher leMessage dans
le champ "resultat"
end
Comme d'habitude,
nous avons tout de suite écrit notre gestionnaire d'événement
mouseUp, presque vide. En effet, nous y avons mis ce que nous avons
déjà conçu :
- l'action décrite dans le mouseUp dépend de la valeur
de tri, d'ou la structure conditionnelle,
- le fonctionnement en bascule implique que chaque clic sur le bouton inverse
la valeur de tri,
- la légende du bouton change en indiquant ce qui se passera dans le
futur si on clique,
- à la fin du traitement, on affiche le résultat, quelle que soit
la valeur de tri (c'est pourquoi cette instruction est en dehors de la structure
conditionnelle).
En ce qui concerne le beginSprite, nous avons initialisé la valeur de tri à #frequence et la légende du bouton (située juste en dessous de lui sur le scénario, donc de numéro spriteNum + 1) à "Trier par nombre d'occurrences". En effet, d'après le cahier des charges, on affiche toujours en premier l'analyse des occurrences par ordre alphabétique. Donc le premier emploi du bouton de choix de critère de tri provoquera donc bien un tri par nombre d'occurrences. D'où les valeurs de l'initialisation.
Le tri, pour un critère ou pour un autre, doit partir,
évidement, de la liste dédoublonnée des mots qui composent
le texte. Nous pourrions bien sûr recalculer cette liste dans ce script,
mais ce serait encore créer de la redondance puisque cette liste a déjà
été calculée par la fonction chercheoccurrences(),
laquelle nous a donné accès, par la même occasion, aux choix
de critère et d'ordre de tri.
De manière à disposer de cette liste dédoublonnée,
et ce quels que soient les choix successifs que nous ferons sur les deux petits
boutons, l'idée est de rendre toujours disponible cette liste, appelée
lstMotsComptes depuis l'étape précédente. Pour
ce faire, et comme d'habitude, nous avons modifié le script désormais
appelé "statsoccurrences" (ex-faireStats),
pour mettre lstMotsComptes en propriété.
Ensuite de quoi, on partira toujours de cette lstMotsComptes pour faire les
manipulations que nous souhaitons.
Pour l'affichage par ordre alphabétique, il n'y a aucun traitement à faire : c'est l'ordre qui est affiché par chercheoccurrences() lorsqu'elle est appelée. Puis, si nous sommes passé au tri par nombre d'occurrences, il suffira de refaire un affichage à partir de lstMotsComptes, qui est restée constante.
Pour ce qui est
du tri par nombre d'occurrences, les choses sont un peu plus complexes. En effet,
les listes de propriétés peuvent être triée par ordre
de propriétés, mais NE PEUVENT PAS être triées par
ordre de valeur.
Une autre solution qui consisterait à intervertir chaque couple en remplaçant
la propriété par la valeur et vice-versa ne convient pas non plus
pour les raisons exposées à l'étape précédente
: pour accéder à une valeur, il faut d'abord accéder au
nom de la propriété en utilisant son rang, et ensuite accéder
à la valeur via le nom de la propriété. Or, dans le cas
présent, du fait que plusieurs mots ont le même nombre d'occurrences,
en renversant les couples, nous aurions des doublons dans ce qui serait devenu
les propriétés. Or nous savons que dans ce cas, la recherche d'une
valeur via le nom de la propriété s'arrête à la première
propriété ayant le nom cherché, et ignore les propriétés
suivantes qui auraient un nom identique.
Nous allons donc écrire un traitement qui laissera inchangés les couples, mais les retriera par valeurs.
Comment faire
?
C'est relativement simple mais il faut procéder en plusieurs étapes.
Nous allons lire la liste lstMotsComptes, élément par
élément. A la première lecture, nous allons mettre de coté
dans une autre liste (appelée dans l'exercice lstTrioccurrences)
tous les mots qui ont une occurrence à 1. Dans le même temps, nous
mettrons ces mots (et les mots seulement) dans une autre liste (appelée
lstElimines dans l'exercice) qui servira à
éliminer ensuite, toutes ces occurrences à 1 : en effet nous utiliserons
pour détruire les couples une fois rangés dans lstTrioccurrences,
la commande deleteProp(unePropriété),
qui prend en argument le nom de la propriété, et détruit
la propriété et sa valeur associée (ici, on voit aussi
la nécessité d'avoir des noms de propriété uniques
dans la liste : si deux propriétés ont le même nom, alors
seule la première est détruite).
Puis nous recommencerons avec une valeur d'occurrence 2, puis 3,etc. jusqu'à
épuisement de la liste lstMotsComptes.
Pour suivre le compte des occurrences, on se dote d'un compteur que j'ai nommé
nboccurrences, lequel compteur est initialisé
à 1 avant le traitement.
Il est clair que, comme d'habitude, ce traitement va se faire au moyen de boucles
de répétition.
Comme nous ne
savons pas quelle est la plus grande occurrence, ni comment elles se répartissent,
nous allons englober la totalité du traitement dans une boucle infinie,
et nous incluerons, dans cette boucle infinie, une instruction qui permettra
d'en sortir.
Une boucle infinie est une boucle dont la condition de sortie ne se réalise
jamais. Par exemple, en parcourant une liste dans une boucle fondée sur
le nombre d'éléments de la liste (repeat while uneListe.count
> 0), et que je ne détruis pas les éléments dans
la boucle, comme le compte n'atteint jamais 0, on ne sortira jamais de la boucle.
Une boucle infinie est normalement un bug dans un programme. En effet, comme,
dans une boucle de répétition, l'application est sourde, aveugle
et muette comme nous l'avons vu au début de ce cours, tomber dans une
boucle infinie implique le gel complet de la machine, du programe, et de la
fonction qu'on tentait d'exécuter.
Toutefois, une boucle infinie peut aussi être une technique de programmation,
quand on ne sait pas a priori combien de temps va durer le traitement, ou sur
combien d'éléments il doit porter. Alors on fait fait une boucle
infinie, mais on n'oublie pas, d'inclure une clause (généralement
exprimée sous forme de condition) qui permet de sortir de la boucle à
l'aide d'une instruction exit repeat. Un exit
repeat provoque une sortie forcée de la boucle.
Dans le cas d'une utilisation consciente d'une boucle infinie, comme ici, le
plus simple est d'écrire :
repeat while 1
......
if
telleCondition then exit repeat
end repeat
Comme 1 est toujours vrai, la boucle est infinie. On en sort quand la condition
telleCondition est remplie.
Ecrivons maintenant nos tris, alphabétiques et par nombre d'occurrences en pseudo-code :
On mouseUp
if
tri = #alphabetique then
![]()
sprite(spriteNum
+ 1).member.text = "Trier par ordre d'occurrences"
![]()
leMessage
= "Ce texte contient"&&sprite(4).lstMotsComptes.count&&"mots
différents, classés par ordre alphabétique."&return&return
![]()
repeat
with i = 1 to sprite(4).lstMotsComptes.count()
![]()
![]()
leMot
= sprite(4).lstMotsComptes.getPropAt(i)
![]()
![]()
laFrequence
= sprite(4).lstMotsComptes.getProp(leMot)
![]()
![]()
if
laFrequence < 10 then
![]()
![]()
![]()
nbPoints
= 6 + (sprite(4).longueurPlusLongMot - sprite(4).lstMotsComptes.getPropAt(i).length)
![]()
![]()
else
![]()
![]()
![]()
nbPoints
= 5 + (sprite(4).longueurPlusLongMot - sprite(4).lstMotsComptes.getPropAt(i).length)
![]()
![]()
end
if
![]()
![]()
lesPoints
= ""
![]()
![]()
repeat
with j = 1 to nbPoints
![]()
![]()
![]()
lesPoints
= lesPoints&"."
![]()
![]()
end
repeat
![]()
![]()
leMessage
= leMessage&"- "&leMot&lesPoints&laFrequence&RETURN
![]()
end
repeat
![]()
tri
= #frequence
else
if tri = #frequence then
![]()
sprite(spriteNum
+ 1).member.text = "Trier par ordre alphabétique "
![]()
créer
une liste de travail qui est une copie de lstMotsComptes du sprite 4
![]()
initialiser
une liste lstTrioccurrences pour stocker le tri par occurrences
![]()
initialiser
un compteur nboccurrences
![]()
initialiser
une liste lstElimines vide pour stocker, à chaque nombre d'occurrences,
les mots à éliminer
![]()
répéter
indéfiniment
![]()
![]()
répéter
avec un compteur i de 1 jusqu'au nombre d'éléments de la lstTravail
![]()
![]()
![]()
leMot
= la propriété de rang i
![]()
![]()
![]()
leNombre
= la valeur associée à leMot
![]()
![]()
![]()
si
leNombre = nboccurrences alors
![]()
![]()
![]()
![]()
ajouter
leMot et leNombre
à lstTrioccurrences
![]()
![]()
![]()
![]()
ajouter
leMot
à lstElimines
![]()
![]()
![]()
fin
si
![]()
![]()
fin
répéter
![]()
![]()
répéter
avec un compteur j de 1 jusqu'au nombre d'élément de lstElimines
![]()
![]()
![]()
détruire,
dans lstTravail, la propriété et sa valeur indiquée au
rang j de la liste lstElimines
![]()
![]()
fin
répéter
![]()
![]()
réinitialiser
la lstElimines à liste vide []
![]()
![]()
incrémenter
de 1 le nboccurrences
![]()
![]()
si
le nombre d'élément de la lstTravail = 0 alors
![]()
![]()
![]()
sortir
de la boucle répéter
![]()
![]()
fin
si
![]()
fin
répéter
![]()
![]()
![]()
leMessage
= "Ce texte contient"&&lstTrioccurrences.count()&&"mots
différents, classés par nombre d'occurrences."&return&return
![]()
repeat
with k = 1 to lstTrioccurrences.count
![]()
![]()
leMot
= lstTrioccurrences.getPropAt(k)
![]()
![]()
laFrequence
= lstTrioccurrences.getProp(leMot)
![]()
![]()
leMessage
= leMessage&"- "&laFrequence&".........."&leMot&return
![]()
end
repeat
![]()
tri
= #alphabetique
end
if
member("resultat").text
= leMessage
end
Remarques
:
- Dans la partie
concernant le tri par ordre alphabétique :
- j'ai indiqué directement le code (sans passer par l'étape du pseudo-code), car c'est exactement le même code que celui que nous avions écrit dans le gestionnaire faireStats() du gros bouton : création de l'en-tête de message, récupération des mots et de leur fréquence, calcul des points de suite, et enfin constituion du message complet. C'est également parce que cette partie est du déjà-vu, que je l'ai tout de suite écrite en petits caractères.
Bien sûr, cette recopie semble déroger au principe du code réutilisable puisque nous créons deux fois le même bloc de code (dans faireStats() et ici). On aurait du normalement écrire une fonction. Mais c'est un peu la facilité qui a primé ici. La fonction aurait été une fonction d'animation (i.e. située dans un script d'animation), on aurait pu l'appeler afficheAlpha(uneListe), on l'appellerait depuis fairestats() et depuis le mouseUp de critère de tri, en lui passant, dans ce dernier cas, sprite(4).lstMotsComptes en argument. On lui demanderait enfin de nous retourner leMessage avec une instruction return.
Ne vous privez pas de faire cette modification à titre d'exercice !
- dans la partie concernant le nombre d'occurrences, il y a plus à dire :
- créer une liste de travail (appelée lstTravail dans l'exercice) :
Pour afficher les occurrences par ordre alphabétique, il n'y avait qu'à lire la liste lstMotsComptes du sprite 4. En revanche, pour faire ce tri par nombre d'occurrence, nous voyons qu'il faut détruire les éléments à mesure qu'on les reclasse. Donc ce tri est destructif au contraire du précédent. Si nous souhaitons garder toujours la liste lstMotsComptes comme référence, il ne faut donc pas faire le traitement de tri par nombre d'occurrences directement sur cette liste. Il faut en faire une copie, qui, elle pourrra être détruite.
Pour faire une copie de liste, il faut utiliser une méthode de liste appelée duplicate(). En effet, si nous écrivions simplement lstTravail = sprite(4).lstMotsComptes, nous ne créérions pas une nouvelle liste, mais une nouvelle instance de la liste lstMotsComptes, portant le nom de lstTravail. Ce qui veut dire qu'en détruisant lstTravail dans le reclassement, nous détruirions en même temps lstMotsComptes, puisqu'en fait les deux seraient la même liste accessible à travers deux noms différents.
Pour créer une nouvelle liste totalement autonome (lstTravail) par rapport à celle qui lui donne naissance (lstMotsComptes), on utilise la méthode duplicate(). En faisant cela, nous pouvons faire subir toutes les tortures à lstTravail, sans que lstMotsComptes en soit affectée.- Pourquoi passer par une liste supplémentaire lstElimines au lieu d'éliminer les couples au fur et à mesure ?
Si nous éliminions les couples au fur et à mesure que nous trouvons ceux qui conviennent et que nous les stockons dans lstTrioccurrences, cela décalerait tous les index des éléments de lstTravail situé après le couple éliminé.
Prenons un exemple et supposons que nous examinons le 14e couple de la liste lstTravail. Supposons également que ce 14e couple corresponde au nombre d'occurrences que nous sommes en train de rechercher, par exemple 1 (ce sont les mots qui n'existent qu'en un seul exemplaire dans la liste des mots).
Le schéma ci-dessous illustre le phénomène : les points de couleur matérialisent les couples de la liste ; au dessus, on a indiqué leurs index.

Pour examiner ce 14e couple, cela veut dire que le compteur i qui décrit la liste lstTravail vaut 14, désignant l'élément violet.
Ce couple, parce qu'il correspond au nombre d'occurrences recherché, nous le copions dans la liste lstTrioccurrences.
Supposons maintenant que nous le détruisions tout de suite.
Alors le couple qui portait le numéro 15 (point noir) dans la liste devient 14 (puisque le 14 originel a été détruit).
Mais à l'exécution suivante de la boucle de répétition, le compteur i, qui doit pointer sur l'élément suivant de la liste, devient, lui, 15.
Et va donc scruter le 15e couple de la liste, qui est maintenant le point cyan.
C'est à dire que le couple représenté par le point noir a été oublié.
Donc, non seulement l'eXtraction nombre d'occurrences par nombre d'occurrences sera fausse, mais encore on ne sortira jamais de la boucle infinie. En effet, au prochain parcours de la liste, on cherchera un nombre d'occurrences incrémenté de 1. C'est à dire que si le point noir oublié correspondait au nombre d'occurrences précédent, il ne sera plus jamais repéré et détruit, et la liste lstTravail ne s'épuisera jamais.
En mettant, lors du parcours complet de la liste (répéter avec un compteur i de 1 jusqu'au nombre d'éléments de la lstTravail...), tous les éléments à supprimer dans une liste annexe lstElimines, et en détruisant tous les couples qui ont été marqués dans lstElimines d'un seul coup APRES chaque parcours complet, on règle le problème. On recommence un nouveau parcours de la liste lstTravail avec un nombre d'occurrences cherché incrémenté de 1 sur une liste qui a été débarrassée de tous les éléments repérés au parcours précédent.
A noter qu'on aurait pu s'affranchir du problème de deux autres manières :
- parcourir la liste en sens inverse, c'est à dire du dernier élément vers le premier : ainsi, on perturbe toujours les index des éléments situés après l'élément supprimé, ce qui n'a aucune importance puisque ceux-ci sont déjà traités. Les index des éléments situé avant l'élément supprimé, eux, ne sont pas affectés.
Mais alors, puisque la liste de travail est triée au départ en ordre alphabétique, on récupérera à l'arrivée une liste lstTrioccurrences dans laquelle les mots seront classés par ordre alphabétique inverse à l'intérieur de chaque valeur d'occurrence, occurrences qui elles, resteraient classées par ordre croissant.
- on n'est en fait pas obligé de détruire les éléments que l'on repère pour un nombre d'occurrences donné, puisque, la comparaison se faisant sur le nboccurrences, qui et incrémenté à chaque parcours, les mots déjà eXtraits seraient simplement ignorés. L'inconvénient est que la liste garde inutilement son nombre total d'éléments (552 dans notre exemple), et qu'on relira l'ensemble des 552 elements à chaque parcours de la liste, alors qu'en supprimant, on allège la liste à chaque parcours. Garder les éléments traités ralentit considérablement le traitement (faites l'essai ! ), alléger la liste optimise le traitement.
Ce
qui donnera en code :
on mouseUp me
if
tri = #alphabetique then
![]()
sprite(spriteNum
+ 1).member.text = "Trier par ordre d'occurrences"
![]()
leMessage
= "Ce texte contient"&&sprite(4).lstMotsComptes.count&&"mots
différents, classés par ordre alphabétique."&return&return
![]()
repeat
with i = 1 to sprite(4).lstMotsComptes.count()
![]()
![]()
leMot
= sprite(4).lstMotsComptes.getPropAt(i)
![]()
![]()
laFrequence
= sprite(4).lstMotsComptes.getProp(leMot)
![]()
![]()
if
laFrequence < 10 then
![]()
![]()
![]()
nbPoints
= 6 + (sprite(4).longueurPlusLongMot - sprite(4).lstMotsComptes.getPropAt(i).length)
![]()
![]()
else
![]()
![]()
![]()
nbPoints
= 5 + (sprite(4).longueurPlusLongMot - sprite(4).lstMotsComptes.getPropAt(i).length)
![]()
![]()
end
if
![]()
lesPoints
= ""
![]()
repeat
with j = 1 to nbPoints
![]()
![]()
lesPoints
= lesPoints&"."
![]()
end
repeat
![]()
leMessage
= leMessage&"- "&leMot&lesPoints&laFrequence&RETURN
![]()
end
repeat
![]()
tri
= #frequence
else
if tri = #frequence then
![]()
sprite(spriteNum
+ 1).member.text = "Trier par ordre alphabétique "
![]()
lstTravail
= (sprite(4).lstMotsComptes).duplicate()
![]()
lstTrioccurrences
= [:]
![]()
nboccurrences
= 1
![]()
lstElimines
= []
![]()
repeat
while 1
![]()
![]()
repeat
with i = 1 to lstTravail.count
![]()
![]()
![]()
leMot
= lstTravail.getPropAt(i)
![]()
![]()
![]()
leNombre
= lstTravail.getProp(leMot)
![]()
![]()
![]()
if
leNombre = nboccurrences then
![]()
![]()
![]()
![]()
lstTrioccurrences.addProp(leMot,
leNombre)
![]()
![]()
![]()
![]()
lstElimines.add(leMot)
![]()
![]()
![]()
end
if
![]()
![]()
end
repeat
![]()
![]()
repeat
with j = 1 to lstElimines.count
![]()
![]()
![]()
lstTravail.deleteProp(lstElimines[j])
![]()
![]()
end
repeat
![]()
![]()
lstElimines
= []
![]()
![]()
nboccurrences
= nboccurrences + 1
![]()
![]()
if
lstTravail.count = 0 then
![]()
![]()
![]()
exit
repeat
![]()
![]()
end
if
![]()
end
repeat
![]()
leMessage
= "Ce texte contient"&&lstTrioccurrences.count()&&"mots
différents, classés par nombre d'occurrences."&return&return
![]()
repeat
with k = 1 to lstTrioccurrences.count
![]()
![]()
leMot
= lstTrioccurrences.getPropAt(k)
![]()
![]()
laFrequence
= lstTrioccurrences.getProp(leMot)
![]()
![]()
leMessage
= leMessage&"- "&laFrequence&".........."&leMot&return
![]()
end
repeat
![]()
tri
= #alphabetique
end
if
member("resultat").text
= leMessage
end
Inversion
de l'ordre de tri :
Ce traitement est très simple.
Il suffit de prendre la liste source que l'on veut renverser, de la lire à
l'envers, du dernier élément vers le premier, et de mettre chacun
des éléments lus dans cet ordre dans une autre liste temporaire.
A la fin, il suffit de remettre le contenu de la liste temporaire dans la liste
source et de l'afficher.
La seule difficulté que nous avons ici est de disposer de la liste source
qui convient.
En effet, ce bouton ne fait que renverser l'affichage courant. Et le renversement
de la liste peut donc se faire sur une liste triée par ordre alphabétique
ou par nombre d'occurrences.
Pour le critère de tri, nous avons utilisé comme liste source
de référence constante, la lstMotsComptes du sprite 4,
stockée en propriété de ce même sprite.
Cette liste source de référence conviendra ici si l'affichage
courant est un affichage par ordre alphabétique. En revanche, si l'affichage
courant est le classement par nombre d'occurrence, utiliser la lstMotsComptes
du sprite 4 comme référence conduirait à refaire le traitement
de changement de critère de tri déjà fait par le bouton
dévolu à cette fonction. Et nous savons qu'il n'est pas bon de
faire deux fois la même chose en des endroits différents.
Il faut donc se doter d'une autre liste source de référence qui
corresponde à l'affichage courant, ordre alphabétique ou par nombre
d'occurrences.
Il faut donc aller mesurer quel est cet affichage courant, et créer cette
nouvelle liste source de référence.
C'est très simple : il faut regarder du coté du bouton du critère
de tri (dans
l'exercice,
contenu dans le sprite 35). Les données du script de ce bouton nous donnent
les renseignements dont nous avons besoin.
Pour
mesurer l'affichage courant, on regarde la propriété tri
du bouton de critère. Cette propriété bascule à
chaque clic sur ce bouton de critère en prévision du tri suivant.
Ce qui veut dire que si tri du sprite 35 est règlé à
#frequence, alors l'affichage courant est alphabétique (réalisé
au précédent clic), et inversement, si tri du sprite
35 est règlé à #alphabetique, l'affichage courant
est classé par nombre d'occurrences.
Pour se doter d'une autre liste source de référence, nous allons
utiliser la même technique que pour le critère de tri. Nous allons
créer une nouvelle propriété du bouton de critère
de tri, qui contiendra la liste à partir de laquelle a été
réalisé l'affichage courant. Nous récupérerons cette
liste dans le le bouton d'ordre de tri pour l'inverser. Dans l'exercice, j'ai
appelé cette propriété du bouton de critère de tri
lstDispoPourInverser.
Le script du bouton de critère de tri est donc modifié comme suit après avoir rajouté lstDispoPourInverser sur la ligne de déclaration de propriété et avoir initié lstDispoPourInverser comme une liste vide dansle beginSprite :
on
mouseUp me
if
tri = #alphabetique then
![]()
sprite(spriteNum
+ 1).member.text = "Trier par ordre d'occurrences"
![]()
lstDispoPourInverser
= (sprite(4).lstMotsComptes).duplicate()
![]()
...(le
reste sans changement)
else
if tri = #frequence then
![]()
...(sans
changement)
![]()
lstDispoPourInverser
= lstTrioccurrences.duplicate()
![]()
...(le
reste sans changement)
end
if
member("resultat").text
= leMessage
end
Ensuite, dans le script du bouton d'inversion d'ordre, nous récupérons cette liste lstDispoPourInverser du sprite 35 (bouton du critère de tri).
Et nous pourrons écrire le script complet du traitement d'inversion (je ne reviens pas sur les mouseEnter, Leave et autres beginSprite) :
property spriteNum, affichageCourant
on
beginSprite
affichageCourant = void
end
on
mouseEnter me
sprite(spriteNum).member
= "petitBAct"
end
on
mouseLeave me
sprite(spriteNum).member
= "petitB"
end
on
mouseUp me
si la propriété
tri du sprite 35 est à #alphabetique amors
![]()
affichageCourant
= #frequence
sinon si la propriété
tri du sprite 35 est à #frequence alors
![]()
affichageCourant
= #alphabetique
fin si
créer
une variable locale appelée laListe et y mettre la copie de lstDispoPourInverser
du sprite 35
exécuter une fonction
renverserListe en lui passant en argument laListe, et récupérer
le résultat dans laListe
si
affichageCourant = #alphabetique alors
![]()
élaborer
leMessage avec en-tête et le contenu de laListe, en réalisant le
calcul des points de suite
sinon si affichageCourant
= #frequence alors
![]()
élaborer
leMessage avec en-tête et le contenu de laListe
fin si
réactualiser
lstDispoPourInverser du sprite 35 avec une copie de laListe
afficher leMessage dans
le champ "resultat"
fin
on
renverserListe(uneListe)
créer une liste
de propriété temporaire appelée lstTempo, et l'initialiser
à vide
répéter avec
un compteur i du nombre d'éléments de uneListe en décroissant
jusqu'à 1
![]()
eXtraire
le mot de rang i
![]()
eXtraire
la valeur associée au mot de rang i
![]()
ajouter
à lstTempo le mot et la valeur eXtraits
fin répéter
dire à la fonction
de renvoyer la lstTempo
fin
Remarques :
- Dans le beginSprite, on initialisé affichageCourant
à une valeur bizarre qu'on appelle void. En
anglais, void veut dire vide. Cette valeur existe dans presque tous
les langages. On emploie cette valeur dans les variables lorsque la valeur contenue
dans la variable est indéterminée. C'est le cas ici, puisque affichageCourant
ne sera réellement définie que lorsqu'on appuiera sur le bouton.
Dans les langages qui exigent de déclarer à l'avance ce que les
fonctions devront renvoyer, le mot void est alors employé pour
indiquer que la fonction ne renvoie aucune valeur : void maFonction{...
- Dans la dernière
étape de l'exercice du memory, nous avions brièvement évoqué
l'instruction return, à ne pas confondre avec
le RETURN que nous employons dans les concaténations de chaîne
pour introduire un saut de ligne.
Cette instruction return sera ici utilisée pour indiquer à
une fonction de renvoyer une valeur. D'autre part, return provoque
la sortie forcée du gestionnaire ou de la fonction dans lesquels elle
est employée. Elle figurera donc en général à la
dernière ligne du code, à moins, que dans certains cas, on ne
souhaite interrompre un traitement quelconque à la survenue d'un événement
par exemple.
Voici comment on l'emploie :
![]()
on
maFonction(arguments éventuels)
![]()
![]()
instructions
et calcul
![]()
![]()
affectation
du résultat des calculs dans une variable locale maValeur
![]()
![]()
return
maValeur
![]()
end
Et voici comment
on récupère maValeur depuis un autre script dans lequel
on appelle la fonction maFonction() :
![]()
on
monGestionnaire
![]()
![]()
instructions...
![]()
![]()
maVariable
= maFonction(arguments éventuels)
![]()
![]()
suite
des instructions
![]()
end
Ci-dessus, le résultat des opérations effectuées dans maFonction est placé dans la variable maVariable (qui peut être locale, globale ou encore une propriété quelconque d'un sprite) de monGestionnaire.
Les fonctions avec
renvoi de valeur existent dans tous les langages.
A noter que, du fait que return provoque la sortie de la fonction,
on ne peut pas mettre deux instructions return à la suite l'une
de l'autre : la seconde serait systèmatiquement ignorée. Si on
a besoin de faire renvoyer plusieurs valeurs à une fonction, alors il
faut mettre ces valeurs dans une liste, et c'est la liste qui sera renvoyée
par la fonction.
Nous pouvons donc coder :
property spriteNum, affichageCourant
on
beginSprite
affichageCourant
= void
end
on
mouseEnter me
sprite(spriteNum).member
= "petitBAct"
end
on
mouseLeave me
sprite(spriteNum).member
= "petitB"
end
on
mouseUp me
if
sprite(35).tri = #alphabetique then
![]()
affichageCourant
= #frequence
else
if sprite(35).tri = #frequence then
![]()
affichageCourant
= #alphabétique
end
if
laListe
= (sprite(35).lstDispoPourInverser).duplicate()
laListe
= renverserListe(laListe)
if
affichageCourant = #alphabétique then
![]()
longueurMot
= 0
![]()
repeat
with i = 1 to laListe.count
![]()
![]()
if
laListe.getPropAt(i).length > longueurMot then
![]()
![]()
![]()
longueurMot
= laListe.getPropAt(i).length
![]()
![]()
end
if
![]()
end
repeat
![]()
leMessage
= "Ce texte contient"&&laListe.count()&&"mots
différents, classés par ordre alphabétique."&return&return
![]()
repeat
with i = 1 to laListe.count()
![]()
![]()
leMot
= laListe.getPropAt(i)
![]()
![]()
laFrequence
= laListe.getProp(leMot)
![]()
![]()
if
laFrequence < 10 then
![]()
![]()
![]()
nbPoints
= 6 + (longueurMot - laListe.getPropAt(i).length)
![]()
![]()
else
![]()
![]()
![]()
nbPoints
= 5 + (longueurMot - laListe.getPropAt(i).length)
![]()
![]()
end
if
![]()
![]()
lesPoints
= ""
![]()
![]()
repeat
with j = 1 to nbPoints
![]()
![]()
![]()
lesPoints
= lesPoints&"."
![]()
![]()
end
repeat
![]()
![]()
leMessage
= leMessage&"- "&leMot&lesPoints&laFrequence&RETURN
![]()
end
repeat
else
if affichageCourant = #frequence then
![]()
leMessage
= "Ce texte contient"&&laListe.count()&&"mots
différents, classés par nombre d'occurrences."&return&return
![]()
repeat
with i = 1 to laListe.count()
![]()
![]()
leMot
= laListe.getPropAt(i)
![]()
![]()
laFrequence
= laListe.getProp(leMot)
![]()
![]()
leMessage
= leMessage&"- "&laFrequence&".........."&leMot&RETURN
![]()
end
repeat
end
if
sprite(35).lstDispoPourInverser
= laListe.duplicate()
member("resultat").text
= leMessage
end
on
renverserListe(uneListe)
lstTempo
= [:]
repeat
with i = uneListe.count() down to 1
![]()
laProp
= uneListe.getPropAt(i)
![]()
laVal
= uneListe.getProp(laProp)
![]()
lstTempo.addProp(laProp,
laVal)
end
repeat
return
lstTempo
end
Bien noter qu'à la fin du mouseUp, on réactualise la lstDispoPourInverser du sprite 35, de manière à disposer, pour une prochaîne exécution du changement d'ordre de tri, d'une liste de référence correcte (bouton en bascule).
Nous avons maintenant terminé nos scripts de classement dans différents ordres.
Avant de passer
à l'étape suivante, il faut juste faire une dernière mise
au point :
dans l'état actuel de notre application, imaginons qu'après avoir
fait les statistiques, nous demandions les occurrences, qui nous serons donc
présentées par ordre alphabétique croissant.
Si nous demandons immédiatement une inversion de l'ordre de tri, les
résultats nous annoncerons "0 mots, classés par ordre alphabétique".
Tout simplement parce qu'à cet instant, lors du premier affichage des
occurrences, la lstDispoPourInverser, dont se sert le bouton d'inversion
de tri, n'a pas encore été peuplée par un clic sur le bouton
des critères de tri.
Il nous faut donc juste rajouter une ligne dans le script du sprite 4 (qui passe
des statistiques à l'analyse par occurrences et vice-versa), juste avant
la fin de la fonction chercheoccurrences(), ligne qui va anticiper le peuplement
de lstDispoPourInverser du sprite 35 :
sprite(35).lstDispoPourInverser
= lstMotsComptes.duplicate()
Nous
avons maintenant terminé cette huitième étape de l'exercice,
huitième étape que l'on pourrait considérer comme aboutie.
Néanmoins, dans l'étape suivante, nous génraliserons notre
recherche à un corpus de textes, ce qui nous donnera l'occasion d'apprendre
à lire des fichiers ASCII directement sur le disque dur. Nous verrons
également comment mettre de l'hypertexte dans un acteur champ.
(*) NB
: Jusque là, le mot propriété
peut désigner trois choses différentes (et ce doit être
tout, il n'y en aura pas de quatrième !) :
- une propriété déclarée ou préconstruite
de sprite,
- la première valeur d'un couple dans les
listes dites de propriétés,
- et enfin une valeur d'un indicateur.
Dans le premier cas, il s'agit d'une entité comparable à une variable
mais dont la portée est limitée au sprite. Elle est indépendante
du type de données (numérique, chaîne, liste...) que nous
y stockons.
Dans le deuxième cas, on est proche du premier cas : il s'agit de conteneur
à valeur, et la liste de propriété nous permet de stocker
à la fois le nom du conteneur et la valeur qu'il contient.
Dans le troisième cas, il s'agit d'un type de données. Ici, une
propriété d sprite vaut une valeur de type propriété.
C'est le contexte qui permet de différencier.
On a beau dire que lingo n'est pas typé, le nombre d'occasions dans lesquelles
il faut malgré tout tenir compte de ces types est finalement assez important.
Quoiqu'il en soit, il vaut mieux, et dans un but de polyvalence sur plusieurs
langages, ne pas perdre de vue le type de données que l'on manipule...
Cela vient tout seul avec un peu d'habitude.
| Retour à la septième étape |