Télécharger le .dir correspondant à cette étape
(bouton droit -> enregistrer la cible)


Huitième (et avant-dernière) é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,
- par nombre d'occurrences, en ordre croissant ou décroissant, et à l'intérieur des séries de mots ayant le même nombre d'occurrences, les mots seront classés par ordre alphabétique croissant.

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
Passer à la neuvième étape