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


Septième étape :

Nous allons maintenant compléter notre outil d'analyse en y ajoutant les fonctionnalités suivantes :
- indiquer le nombre de mots différents qu'on trouve dans le texte,

- les classer par ordre alphabétique et indiquer leur nombre d'occurrences dans le texte

Pour analyser les occurrences, nous continuerons à utiliser le bouton dont nous nous servions pour faire les statistiques du texte. Ce bouton fonctionnera en bascule : lorsqu'il aura fait les statistiques, le bouton proposera d'analyser les occurrences, lorsque les occurrences auront été analysées, le bouton proposera à nouveau de faire les statistiques, etc.

La légende de ce bouton devra changer en fonction de l'état de l'application.

 

Pour obtenir le résultat décrit ci-dessus, nous allons devoir modifier et compléter le script du deuxième gros bouton, qui ne nous servait jusque là qu'à faire les statistiques du texte.

Deux affichages sont possibles dans la fenêtre de résultat :
- les statistiques du texte,
- les occurrences et leur fréquence.

Ceci veut dire que le bouton va déclencher alternativement la méthode faireStats(), ou la nouvelle méthode, que nous allons construire plus loin, qui permet d'analyser les occurrences, et que j'ai appelée dans l'exercice chercheoccurrences().
Nous retrouvons donc bien ici la notion de mode que nous avions mise en place dans l'exercice de l'avion : dans l'un des modes, numéroté 1, le bouton fait les statistiques du texte, dans l'autre, numéroté 2, le bouton analyse les occurrences.
D'autre part, pour guider notre utilisateur, la légende du bouton doit changer : lorsque les statistiques sont affichées, le bouton doit montrer comme légende "Analyser les occurrences...", et lorsque rien n'est affiché (au démarrage) ou que les occurrences sont affichées, le bouton doit montrer comme légende "Faire les statistiques du texte...".
En effet, cette légende indique ce qui se passera (dans le futur) si on clique sur le bouton.

Le bouton doit donc changer de mode quand on le clique, c'est à dire qu'on en fait une bascule : un premier clic, le mode étant 1, et je fais les statistiques, un deuxième clic, le mode ayant été mis à 2 par le clic précédent, et j'analyse les occurrences, un troisième clic, le mode étant revenu à 1 du fait du clic précédent, et je refais les statistiques, etc.

Ainsi, on trouvera des modifications dans le script du gros bouton (celui qui passe des statistiques aux occurrences et vice-versa) aux niveaux suivants:
- ajout de propriétés (au moins leMode dans un premier temps mais nous verrons que nous devrons en créer d'autres dont nous découvrirons la nécessité à mesure que nous progresserons dans la construction des fonctionnalités),
- modification du beginSprite (ne serait-ce que parce que, du fait de l'apparition de nouvelles propriétés, ces dernières devront être initialisées, mais il nous faudra aussi donner des instructions de présentation d'interface),
- modification du mouseUp,
- ajout d'une méthode pour la recherche des fréquences d'occurrence, chercheoccurrences(), méthode que, comme à notre habitude, nous allons écrire en la laissant vide pour l'instant.
A noter que la méthode existante faireStats(uneChaîne, unTexte), elle, ne change pas, car nous n'introduisons aucune modification dans cette fonctionnalité.

Nous allons donc pouvoir réécrire notre script complet de la manière suivante, en pseudo-code :

property spriteNum, leMode

on beginSprite
mettre leMode à 1
afficher comme légende du bouton "Faire les statistiques du texte"
end

on mouseEnter, on mouseLeave et on mouseDown
sans changement
end

on mouseUp

remettre tout le texte en style normal et en gras

Dans le cas où leMode vaut
1 :
faireStats() sur le texte contenu dans l'acteur "leTexte"
changer la légende du bouton en "Analyser les occurrences..."
mettre leMode à 2

2 :
chercher les occcurences dans le texte contenu dans l'acteur leTexte, en appelant la méthode chercheoccurrences()
changer la légende du bouton en "Faire les statistiques du texte..."
mettre le mode à 1
fin des cas

end

on faireStats(unTexte)
sans changement
end

on chercheoccurrences me
vide pour l'instant
end

Remarques :
- leMode du bouton est mis à 1 dans le beginSprite, et sa légende est fixée à "Faire les statistiques du texte". En effet, au démarrage de l'application, et/ou au moment où on utilisera la première fois ce bouton "stats/occurrences", le champ resultat est soit vide (on n'a encore rien touché), soit contient les résultats d'une recherche de sous-chaîne (si on a utilisé cette fonctionnalité auparavant). Donc, dans tous les cas, au moment de se servir la première fois de ce bouton, sa mission est de faire les statistiques du texte.
- en première ligne du mouseUp, on remet en noir et en style normal le champ leTexte. En effet, si on a par hasard fait une recherche de sous-chaîne avant de faire les statistiques du texte, il ne faut pas que les caractères qui auraient été mis en rouge et en gras par la recherche, le restent. Donc dans tous les cas, on rectifie la couleur et le style du texte, et la ligne d'instructions correspondantes est placée avant d'entrer dans l'examen de la valeur de leMode.

Ce qui nous donnera en code :

property spriteNum, leMode

on beginSprite
leMode = 1
member("legende2").text = "Faire les statistiques du texte..."
end

on mouseEnter, on mouseLeave et on mouseDown
sans changement
end

on mouseUp

set the foreColor of member("leTexte") to the forecolor of member("noirRef")
set the fontStyle of member("leTexte") to "plain"

case leMode of
1 :
faireStats(member("leTexte").text)
member("legende2").text = "Analyser les occurrences..."
leMode = 2

2 :
chercheoccurrences()
member("legende2").text = "Faire les statistiques du texte..."
leMode = 1
end case

end

on faireStats(unTexte)
sans changement
end

on chercheoccurrences me
Vide pour l'instant
end

Voyons maintenant quel va être le traitement que nous allons faire dans la méthode chercheoccurrences().

Dans la précédente méthode faireStats(), nous avions "haché" notre texte de manière à en eXtraire tous les mots, débarassés de tous les autres signes (ponctuations, espaces, sauts de ligne, etc.).
Dans la liste des mots que nous en avions alors constituée (lstMots), les mots figurent autant de fois qu'ils sont employés dans le texte. Cette liste de mots comprend donc des doublons.
Dans la recherche de fréquence des occurrences, nous cherchons à savoir combien de fois chaque mot différent figure dans la liste des mots.
Cela veut dire :
- que nous allons devoir éliminer les doublons pour ne garder qu'un seul exemplaire de chaque mot différent (on dit "dédoublonner" : sport favori des gestionnaires de bases de données où les doublons sont un fléau !),
- mais que, dans le même temps, il nous faudra compter le nombre d'exemplaires de chaque mot différent, et trouver un moyen de garder associés tel mot et son nombre d'occurrences. Chaque mot devra être lié de manière fixe et définitive à sa fréquence.

Deux questions se posent alors :
- trouver un modèle d'entité qui nous permette de modéliser une liste de couples mot/fréquence,
- ce modèle d'entité doit permettre la manipulation de ces couples sans jamais séparer le mot de sa fréquence.

Avant de nous intéresser au traitement de dédoublonnage et de comptage, voyons quels pourraient être les modèles d'entité que nous pourrions adopter pour stocker nos couples mot/fréquence.

Tout d'abord, mentionnons que nous allons devoir trier la liste des mots.
Nous allons devoir la trier d'une part pour la présentation des résultats de l'analyse des occurrences, puisque nous présentons ce résultat par ordre alphabétique, et d'autre part, nous verrons plus bas que le tri préalable, sans être totalement indispensable au traitement de dédoublonnage, le simplifie considérablement.
Il existe heureusement une instruction dans les langages qui permet de trier une liste ou un tableau.
Si la liste ou le tableau contient des chaînes de caractères, c'est à dire des valeurs alphanumériques, alors les valeurs de la liste ou du tableau sont triées par ordre alphabétique croissant.
Si la liste ou le tableau contient des valeurs numériques, alors le tri s'effectue par ordre de valeurs numériques croissantes.
Si la liste contient un panachage de chaînes et de valeurs numériques (ce qui est possible en Lingo mais pas forcément dans d'autres langages où les tableaux doivent être homogènes en terme de types de données), alors la liste, un fois triée, contient au début toutes les valeurs numériques triées par ordre croissant, et ensuite, toutes les chaînes triées par ordre alphabétique croissant.
Si la liste contient d'autres listes, alors le tri s'effectuera sur la première valeur des listes qui constituent les éléments de la liste principale, et ceci selon les même règles que pour des éléments simples.
Cette instruction, en lingo, s'appelle sort(). C'est une méthode d'objet liste, donc nous écrirons au moment de trier la liste nommée uneListe par exemple : uneListe.sort()

Maintenant que notre liste est triée, elle contient toujours tous les mots, mais les mots identiques sont regroupés et figurent dans la liste les uns à la suite des autres. Voici le début de la liste des mots, tout d'abord non triée, puis le début de cette même liste, une fois triée (pour plus de lisibilité, nous avons mis les groupes de mots identiques en couleur et en gras) :

- non triée : ["Le", "meurtre", "de", "Calas", "commis", "dans", "Toulouse", "avec", "le", "glaive", "de", "la", "justice", "le", "9", "mars", "1762", "est", "un", "des", "plus", "singuliers", "événements", "qui", "méritent", "l", "attention", "de", "notre", "âge", "et", "de", "la", "postérité", "On", "oublie", "bientôt", "cette", "foule", "de", "morts", "qui", "a", "péri", "dans", "des", "batailles", "sans", "nombre", "non", "seulement", "parce", "que", "c", "est", "la", "fatalité", "inévitable", "de", "la", "guerre", "mais", "parce", "que", "ceux", "qui", ...

- triée : ["1762", "9", "a", "a", "a", "a", "a", "a", "à", "à", "à", "à", "à", "à", "à", "à", "à", "à", "à", "à", "à", "à", "à", "à", "abjuration", "abjuration", "abjuré", "absurde", "accusé", "acquittaient", "affaiblit", "affaire", "âge", "âgé", "agissait", "agissait", "aider", "aîné", "ainsi", "ajoutèrent", "alla", "allaient", "alors", "Alors", "ami", "ami", "ami", "ami", "ami", "amis", "années", "ans", "ans", "ans", "ans", "Antoine", "Antoine", "Antoine", "Antoine", "Antoine", "Antoine", "Antoine", "Antoine", "apoplectique", "approuva", "après", "après", "Après", ...

NB : les occurrences "1762" et "9" ne sont pas traitées comme des valeurs numériques puisqu'elles font partie d'une chaîne de caractère. On constate néanmoins deux choses :
- les chiffres traités comme caractères viennent en tête de l'ordre alphabétique,
- pour les caractères de "0" à "9", le tri par ordre alphabétique correspond au tri par valeurs numériques. Mais pour les nombre de plus d'un chiffre, alors c'est la suite des caractères numériques qui est prise en compte et non pas la valeur numérique du nombre. Ainsi, "1762", qui commence par "1" vient avant "9", alors que la valeur numérique de 1762 est plus grande que la valeur numérique de 9.(*)
D'autre part, remarquons que le tri par ordre alphabétique n'est pas sensible à la casse, et les capitales ainsi que les bas de casse sont traitées dans le même ordre.

Dernière mention à propos des tris : une fois qu'une liste est triée, elle le reste. Si on ajoute des éléments supplémentaires à la liste, ceux-ci se placeront à l'endroit qui va bien du point de vue du tri, et non pas à la fin de la liste, comme cela se passe avec une liste non triée.
La seule manière de "détrier" une liste, c'est d'écrire un traitement que nous avons déjà rencontré dans l'exercice du mémory, où il nous fallait, pour initialiser le jeu, "battre les cartes".

Voyons maintenant comment dédoublonner la liste et compter les occurrences.

Nous avons dit plus haut qu'il nous fallait stocker ensemble les couples mots/nombre d'occurrences, sans qu'on puisse désolidariser l'un de l'autre, c'est à dire que chaque mot doit être attaché de manière permanente et sure à son nombre d'occurrences. D'autre part, comme nous avons un grand nombre de mots, l'entité la mieux adaptée parait être une liste.
Lingo nous fournit un modèle de liste dans lequel on peut stocker des éléments à 2 valeurs : ce sont les listes de propriétés.
Les listes de propriétés sont des listes qui savent stocker des couples de valeurs, lesquelles, du point de vue syntaxique, sont séparées par le signe deux-points ( : ). Dans la liste, chaque couple est séparé par une virgule comme dans les listes que nous avons déjà utilisées, et qui sont appelées en lingo des listes linéaires.
La première valeur du couple (à gauche des ":") est appelée propriété, la deuxième (donc à droite des ":") est appelée valeur.

Nous allons donc stocker chaque mot différent en propriété, et chaque nombre d'occurrences en valeur associée au mot.

ATTENTION : les propriétés employées à propos des listes n'ont RIEN A VOIR avec les propriétés de sprite que nous utilisons déjà depuis le début de ce cours. Même si ces deux entités portent le même nom générique, il faut faire très attention à ne pas les confondre. Le contexte doit indiquer si on parle de l'une ou de l'autre, sans quoi il faudra toujours préciser.

Les deux autres solutions auraient pu être :
- deux listes linéaires dont les éléments se correspondent rang à rang. Ce modèle ne convient pas car le tri casserait la correspondance de rang à rang. Ce modèle convient lorsque les listes, une fois constituées restent toujours dans le même ordre.
- une liste linéaire dont les éléments seraient chacun une autre liste linéaire de deux éléments : le mot et sa fréquence. Ce modèle peut convenir. Les manipulations sont peut-être un peu plus lourdes qu'avec une liste de propriétés, mais il fonctionnerait. Dans l'exercice, nous avons choisi d'utiliser les listes de propriétés, ne serait-ce pour en avoir entendu parler.

NB : Les listes de propriétés ne sont pas forcément aisées à manipuler, notamment lorsqu'il est question de les trier en divers ordres. En effet, s'il est possible de les trier par ordre de propriétés, on ne peux pas les trier par ordre de valeurs sans un traitement qu'il nous faut écrire nous-mêmes : nous aurons ce problème lorsque nous voudrons présenter les résultats triés par ordre d'occurrences, à l'étape suivante. Mais dans le cas présent, les listes de propriétés répondent à nos besoins.
Autre inconvénient des listes de propriétés par rapport aux tableaux classiques que l'on trouve habituellement dans les langages, c'est qu'elles sont limitées à des ensembles de 2 valeurs : elles sont à deux dimensions. On peut dépasser cette limitation en utilisant des listes de listes (les éléments de la liste principale sont eux-mêmes des listes, linéaires ou de propriétés), mais alors le modèle risque de devenir rapidement complexe et exigera une grande attention pour ne pas se tromper dans les manipulations.
Pour simplifier, les listes de propriétés conviennent bien si chaque propriété est unique dans la liste. Les choses se compliquent sensiblement si il existe des doublons dans les propriétés.

Enfin, si certaines commandes de liste sont applicables aux deux types, linéaire et de propriété, chacun de ces deux types dispose aussi d'un jeu d'instructions qui lui est propre.

Voici quel va être le principe du traitement de dédoublonnage (mais on peut en trouver d'autres), et pour commencer, sans comptage :
- nous allons regarder dans la liste des mots, les deux premiers mots,
- deux cas sont possibles : ces deux éléments sont identiques, ou ils sont différents.

- s'ils sont identiques, cela veut dire que nous sommes en présence d'un doublon. On peut alors supprimer le premier élément de la liste. L'élément qui était le deuxième de la liste est alors devenu le premier.
On regarde donc à nouveau les éléments de rang 1 et 2. Deux cas sont possibles, etc...

- s'ils sont différents, cela veut dire que le premier élément est unique (puisque la liste est triée) et qu'il doit donc être conservé.

Là, deux manières de faire s'offrent à nous :
- soit on utilise un pointeur qui augmentera d'une unité lorsqu'on trouve un mot unique, et qui permettra donc, pour le pas suivant du traitement, de regarder les éléments 2 et 3 (puisque le premier est unique, on le laisse dans la liste et on passe à l'examen des mots suivants), puis 3 et 4, puis 4 et 5, et ainsi de suite à chaque fois qu'on trouve un mot unique,
- mais le plus simple est de se doter d'une deuxième liste, que nous pourrons appeler lstMotsComptes (c'est le nom que j'ai adopté dans l'exercice, mais le comptage ne sera vu qu'un peu plus bas), qui sera initialisée comme une liste vide avant de commencer le traitement. Lorsque les éléments 1 et 2 de la liste source seront différents, et que donc le premier élément est unique, on ajoutera cet élément à lstMotsComptes, et on le supprimera de la liste source. De cette manière, dans la liste source, on regardera toujours, jusqu'au bout du traitement, les éléments 1 et 2.

L'ensemble du traitement se fera dans une boucle de répétition, jusqu'à ce que la liste source ne comprenne plus qu'un seul élément : puisqu'on détruit à chaque fois le premier élément, on aura forcément épuisé la liste source quand on aura détruit un à un tous les éléments de la liste source, sauf le dernier. Ce traitement sera totalement identique à chaque pas jusqu'à ce qu'il ne reste que deux éléments dans la liste. Lorsqu'il ne reste que deux éléments, on ne peut plus enlever le premier et regarder les deux suivants puisqu'en enlevant le premier, il ne reste plus qu'un seul élément dans la liste.
Le traitement à faire alors, lorsqu'il ne reste que deux éléments dans la liste, est de les comparer :
- s'ils sont identiques, on détruit le premier et on ajoute le dernier qui reste à la listeDesMotsComptes (et alors le count de la listeDesMotsComptes est égal à 1 donc on sort de la boucle de répétition), et
- s'ils sont différents, on ajoute ces deux derniers éléments à la liste listeDesMotsComptes, et le traitement est terminé.
Toutefois, pour pouvoir sortir de la boucle de répétition (qui s'exécute tant que la liste source contient plus d'un élément), il faut détruire le premier ces deux derniers éléments de la liste source, de manière à mettre son count() à 1.

Ce qui pourra s'écrire en pseudo-code (du fait qu'il y a beaucoup d'imbrications, j'ai mis les structures de code qui se correspondent dans une couleur différente) :
NB : je donne tout de suite le nom de chercheroccurrences() à la méthode, puisque c'est ce à quoi elle va servir quand nous l'aurons complétée et terminée.

on chercheoccurrences me
trier lstMots
lstMotsComptes = []

repéter tant que lstMots contient plus d'1 élément
mot1 = le premier mot de lstMots
mot2 = le deuxième mot de lstMots

si le nombre d'éléments de lstMots > 2 alors
si mot1 = mot2 alors
détruire le premier mot de lstMots
sinon
ajouter mot1 à listeDesMotsComptes
détruire le premier mot de lstMots
fin si

sinon si le nombre d'éléments de lstMots = 2 alors
si mot1 = mot2 alors
détruire le premier mot de lstMots
ajouter mot2 à lstMotsComptes
sinon
ajouter mot1 à lstMotsComptes
ajouter mot2 à lstMotsComptes

détruire le premier mot de lstMots
fin si

fin si
fin répéter
end

Ce qui pourra se coder (en reprenant les mêmes couleurs pour distinguer les structures du code) :

on chercheoccurrences me
lstMots.sort()
lstMotsComptes = []

repeat while lstMots.count() > 1
mot1 = lstMots[1]
mot2 = lstMots[2]

if lstMots.count() > 2 then
if mot1 = mot2 then
lstMots.deleteAt(1)
else
lstMotsComptes.add(mot1)
lstMots.deleteAt(1)
end if

else if lstMots.count() = 2 then
if mot1 = mot2 then
lstMots.deleteAt(1)
lstMotsComptes.add(mot2)
else
lstMotsComptes.add(mot1)
lstMots.deleteAt(1)
lstMotsComptes.add(mot2)
end if

end if
end repeat

end

Avant de commencer le traitement de dédoublonnage ci-dessus, il aurait normalement fallu reconstituer la liste lstMots. En effet, dans l'étape précédente, cette liste lstMots n'était qu'une simple variable locale, qui servait jusqu'à la fin du script faireStats() pour élaborer le message de résultat. Nous n'utilisions plus lstMots ailleurs, et il n'y avait donc aucune raison de la faire perdurer.
Pour en disposer à nouveau ici, nous aurions pu réécrire le traitement de construction de lstMots, et nous aurions donc été conduits à écrire deux fois la même partie de code : une fois dans faireStats(), et une autre fois dans chercheoccurrences(). Ce qui est mauvais, nous le savons.
Répétons-le une fois de plus, dès que l'on s'aperçoit qu'on est train d'écrire du code que l'on a déjà écrit ailleurs, alors il faut repenser la conception pour éviter cette répétition.
Dans le cas présent, nous pourrions sortir le traitement de constitution de lstMots, pour en faire une fonction, laquelle serait appelée depuis faireStats() là où nécessaire, et qui serait à nouveau appelée depuis chercheoccurrences().
Ce serait déjà mieux, mais il y a plus simple et plus optimisé.
Puisque nous devons passer par faireStats() (et donc fabriquer la liste lstMots) AVANT de pouvoir accéder à la recherche d'occurrences (cahier des charges), alors il nous suffit de stocker lstMots dans une entité qui cette fois, perdure, c'est à dire que nous allons en faire une propriété du bouton.
Pour écrire ce début de chercheoccurrences(), et pour qu'il fonctionne, nous aurons donc rajouté lstMots sur la ligne de déclaration des propriétés, et nous aurons initialisé cette propriété comme une liste vide dans le beginSprite (je ne reproduis pas les lignes correspondantes ici : ce sont des choses que nous avons déjà faites, et on trouvera le script modifié dans le .dir téléchargeable).

Cela réduit le nombre de lignes de notre code. En outre, ce code est optimisé : dans le cas d'une fonction appelée, le calcul serait refait à chaque fois que l'on a besoin de lstMots. Dans la solution ici adoptée, le calcul est fait une fois pour toutes. Le recours à lstMots occupe de la mémoire, mais son appel ne sollicite plus le processeur.

A noter que bien que le traitement de dédoublonnage détruise la liste lstMots, il n'est pas nécessaire d'en faire une sauvegarde avant le traitement de dédoublonnage. En effet, dans les utilisations ultérieures du bouton, il faudra repasser par les statistiques du texte, donc la liste lstMots sera recalculée et reconstituée avant qu'on en ait besoin à nouveau.

Nous reste à implémenter le comptage de chaque occurrence, et à élaborer le message de résultat.
C'est assez simple, mais à cette occasion, nous allons découvrir une première instruction propre aux listes de propriétés : celle qui permet d'ajouter, en une seule fois, une propriété et sa valeur associée :
cette instruction s'écrit uneListeDePropriétés.addProp(unePropriété, saValeur)
Nous allons aussi devoir modifier la déclaration de la variable lstMotsComptes. Nous l'avions déclarée comme une liste linéaire, il va falloir mainenant la déclarer comme une ligne de propriétés : au lieu d'écrire lstMotsComptes = [], nous allons écrire lstMotsComptes = [:].

Ensuite, il nous faudra un compteur. Initialisé à 0 avant de rentrer dans la boucle de dédoublonnage, ce compteur sera incrémenté de 1 à chaque fois que l'on trouve une occurrence de doublon.
En revanche, lorsque nous aurons affaire à un mot unique, soit que nous ayons épuisé toutes les occurrences d'un mot, soit que l'on ait effectivement affaire à un mot unique, nous devrons réinitialiser le compteur à 0 pour pouvoir recommencer le comptage sur le mot suivant.
Les opérations sur le compteur seront toutefois un peu différentes lorsqu'il ne reste plus que deux éléments dans lstMots : si les deux mots qui restent sont identiques, alors c'est un doublon, donc nous avons fini le comptage, et en même temps que nous n'ajoutons en propriété qu'un seul exemplaire de ce mot à lstMotsComptes, il faut incrémenter le compteur de 2 avant de l'inclure dans lstMotsComptes comme valeur associée à ce mot, puisque nous sommes en présence de deux de ses occurrences.
Si en revanche les deux mots sont différents, alors on ajoute le premier à lstMotsComptes, on incrémente son compteur de 1, et on l'inscrit dans lstMotsComptes comme valeur de ce premier mot. Puis nous ajoutons le deuxième mot restant, avec un valeur de 1, puisqu'il reste seul et donc unique.

Pour l'élaboration du message, on opére de la même manière que pour l'affichage des statisques, sauf que nous devons cette fois afficher le contenu entier d'une liste. On initialise donc une variable locale contenant l'en-tête du message ("ce texte contient tant de mots différents, classés par ordre alphabétique"). On rajoute ensuite, à l'aide d'une boucle de répétition balayant la liste entière, autant de lignes (sans oublier le saut) qu'il y a de mots différents, chaque ligne étant composée du mot, de quelques points de suite, et de la fréquence de ce mot.

Nous rencontrons cette fois un problème de présentation. En effet, à moins d'utiliser une police à chasse fixe (non proportionnelle) dans laquelle la largeur d'un mot est déterminée par son nombre de caractères, il n'est pas possible de savoir quelle est la largeur, en terme de place physique, en nombre de pixels par exemple, qu'occupe un mot. On ne peut donc pas aligner verticalement les fréquences.
A noter d'ailleurs que les tabulations ne sont pas honorées dans l'affichage d'un champ texte si on les intercale à l'aide d'un script (avec la constante TAB ou par la conversion numToChar(9) ). Elles semblent prises en compte lorsqu'on les tape au clavier directement en éditant l'acteur champ, mais à l'affichage sur la scène, Director les remplace par des carrés, signe jocker utilisé pour matérialiser les caractères non-imprimables.
Quoi qu'il en soit, si on les tape au clavier, elles ne sont de toutes façons pas calées sur des taquets fixes, mais se contentent d'insérer un espace large : à l'arrivée, on trouve le même décalage introduit par la police à chasse variable.
On peut atténuer le phénomène en introduisant un calcul du nombre de points de suite en fonction du nombre de caractères de chaque mot (ce qui a été fait dans l'exercice), mais un peu inutilement du point de vue de la présentation du résultat.
Pour cela il nous faut calculer la longueur du mot le plus long dans lstMotsComptes au moyen d'une boucle de répétition. La longueur du mot le plus long de la liste, appelée longueurPlusLongMot est initialisée à 0 avant la boucle.
Ensuite, on parcourt la liste, on regarde la longueur de chaque mot. Si celle-ci est supérieure à la valeur actuelle de longueurPlusLongMot, alors, longueurPlusLongMot prend la valeur du nombre de caractères du nouveau mot, qui est plus long que tous ceux qu'on avait trouvés jusque là. A la fin de la boucle, longueurPlusLongMot contient le nombre de caractères du mot le plus long de la liste.
Puis, dans la boucle d'élaboration du message, on calculera un nombre de points de suite, qui ne soit pas inférieur à 5, et qui d'autre part tient également compte du fait que la fréquence est un nombre à un chiffre ou à deux chiffres.
Cela ne donne pas un résultat très satisfaisant, mais on pourra faire l'essai d'attribuer à ce champ un police à chasse fixe, et on constatra alors que ce calcul fonctionne bien.

Ce qui va nous donner, en pseudo-code (seuls les ajouts par rapport à la version précédente sont écrits en pseudo-code) :
on chercheoccurrences me

lstMots.sort()
lstMotsComptes = [:]
compteur = 0

repeat while lstMots.count() > 1
mot1 = lstMots[1]
mot2 = lstMots[2]

if lstMots.count() > 2 then
if mot1 = mot2 then
lstMots.deleteAt(1)
incrémenter le compteur de 1

else
incrémenter le compteur de 1
ajouter à la lstMotsComptes mot1 et la valeur du compteur

lstMots.deleteAt(1)
réinitialiser le compteur à 0
end if

else if lstMots.count() = 2 then
if mot1 = mot2 then
lstMots.deleteAt(1)
incrémenter le compteur de 2
ajouter à la lstMotsComptes mot2 et la valeur du compteur
else
incrémenter le compteur de 1
ajouter à lstMotsComptes mot1 et la valeur du compteur

lstMots.deleteAt(1)
ajouter à lstMotsComptes mot2 et la valeur 1
end if

end if
end repeat

longueurPlusLongMot = 0
répéter avec un compteur i de 1 jusqu'au nombre d'éléments de lstMotsComptes
si le nombre de caractères du mot (propriété) de rang i dans lstMotsComptes est plus long que longueurPlusLongMot alors
longueurPlusLongMot = le nombre de caractères du mot de rang i dans lstMotsComptes
fin si
fin répéter

leMessage = "Ce texte contient" auquel on ajoute le nombre d'éléments de lstMotsComptes auquel on ajoute "mots différents, classés par ordre alphabétique." auquel on ajoute deux retour chariot (pour laisser un interligne)

répéter avec un compteur i de 1 jusqu'au nombre d'éléments de lstMotsComptes
leMot = le mot (propriété) de rang i dans lstMotsComptes
laFrequence = la valeur associée à leMot dans lstMotsComptes

si laFrequence est inférieure à 10 (donc un seul caractère)
nombre de points de suite à insérer = 6 + (longueurPlusLongMot - la longuer de leMot)
sinon
nombre de points de suite à insérer = 5 + (longueurPlusLongMot - la longuer de leMot)
fin si

Les points de suite = une chaîne vide
répéter avec un compteur j de 1 jusqu'à nombre de points de suite à insérer
les points de suite = les points de suite auquel on ajoute un point supplémentaire
fin répéter

leMessage = leMessage auquel on ajoute un tiret et un espace auquel on ajoute leMot auquel on ajoute le nombre de points de suite à insérer auquel on ajoute laFrequence auquel on ajoute un saut de ligne

fin répéter

fixer la propriété text de l'acteur champ resultat à leMessage
end

Nous allons maintenant pourvoir coder cette méthode chercheoccurrences() en entier.
Nous avons déjà vu que pour ajouter un couple propriété/valeur à une liste de propriétés, on utilisait une instruction uneListeDepropriétés.addProp(unePropriété, saValeur).
Nous allons également avoir besoin, maintenant, de récupérer des couples propriété/valeur dans notre liste de propriétés. Pour les listes linéaires, c'était facile : il suffisait d'utiliser les crochets : telleValeurDeTelRang = uneListeLinéaire[telRang].
Pour les listes de propriétés, c'est un peu plus compliqué : il existe une instruction qui permet de récupérer la propriété qui se trouve à tel rang dans la liste (l'équivalent des crochets pour les listes linéaires). Cette instruction est : uneListeDePropriétés.getPropAt(leRang)
Ensuite, une fois que l'on connait la propriété, on peut récupérer la valeur qui lui est associée, à l'aide d'une instruction qui est : uneListeDePropriétés.getProp(unePropriété)
Mais on NE PEUT PAS récupérer seulement une valeur en indiquant son rang dans la liste de propriétés. On ne peut pas non plus récupérer d'un seul coup une propriété et sa valeur en indiquant le rang du couple.

Ceci veut dire :
- qu'on peut toujours récupérer toutes les propriétés d'une liste de propriétés, même s'il existe des propriétés de même nom, puisqu'on accède à ces propriétés par leur rang dans la liste,
- que, si il existe plusieurs propriétés de même nom dans la liste, on ne pourra accéder qu'à la valeur de la première propriété dans la liste d'un nom donné, et jamais aux valeurs associées aux propriétés suivantes (d'un rang plus élevé dans la liste) de même nom dans la liste. En effet, l'instruction getProp renvoie la valeur de la première propriété rencontrée, et ignore les éventuelles suivantes.
C'est la raison pour laquelle j'avais mentionné, en introduction aux listes de propriétés, que celles-ci pouvaient convenir à condition que chaque propriété soit unique dans la liste.

Donc, voici notre méthode chercheoccurrences() une fois codée :

on chercheoccurrences me

lstMots.sort()
lstMotsComptes = [:]
compteur = 0

repeat while lstMots.count() > 1
mot1 = lstMots[1]
mot2 = lstMots[2]

if lstMots.count() > 2 then
if mot1 = mot2 then
lstMots.deleteAt(1)
compteur = compteur + 1
else
compteur = compteur + 1
lstMotsComptes.addProp(mot1, compteur)

lstMots.deleteAt(1)
compteur = 0
end if

else if lstMots.count() = 2 then
if mot1 = mot2 then
lstMots.deleteAt(1)
compteur = compteur + 2
lstMotsComptes.addProp(mot2, compteur)
else
compteur = compteur + 1
lstMotsComptes.addProp(mot1, compteur)

lstMots.deleteAt(1)
lstMotsComptes.addProp(mot2, 1)
end if

end if
end repeat

longueurPlusLongMot = 0
repeat with i = 1 to lstMotsComptes.count
if lstMotsComptes.getPropAt(i).length > longueurPlusLongMot then
longueurPlusLongMot = lstMotsComptes.getPropAt(i).length
end if
end repeat

leMessage = "Ce texte contient"&&lstMotsComptes.count()&&"mots différents, classés par ordre alphabétique."&return&return

repeat with i = 1 to lstMotsComptes.count()
leMot = lstMotsComptes.getPropAt(i)
laFrequence = lstMotsComptes.getProp(leMot)
if laFrequence < 10 then
nbPoints = 6 + (longueurPlusLongMot - lstMotsComptes.getPropAt(i).length)
else
nbPoints = 5 + (longueurPlusLongMot - 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

member("resultat").text = leMessage
end

Nous avons maintenant terminé cette première partie de l'analyse des occurrences.
Dans l'étape suivante, nous rajouterons les choix de critère et d'ordre de tri de ces occurrences.

(*) A noter qu'il faut prendre en compte ce système de classement lorsqu'on est amené à traiter des séries de fichiers (comme une exportation image par image d'une vidéo par exemple), et qu'il faut les nommer. En effet, on les nomme en général à l'aide d'un numéro d'ordre, précédé éventuellement d'un préfixe ou suivi d'un suffixe constant.

Si on nomme ces fichiers "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"... ,le système d'exploitation les présentera triés par ordre alphabétique (et c'est aussi l'ordre de tri que reprendra Director dans la distribution au moment d'importer ces fichiers), puisqu'un nom de fichier est une chaîne.
Donc les fichiers seront dans l'ordre suivant :
"1", "10", ...ici toutes les dizaines, puis toutes les centaines..., "2", "20", ici toutes les vingtaines et les deux-centaines..., "3", "30", "31", "32", ... ici toutes les trentaines, puis les trois-centaines...
Si on a des milliers, le premier millier sera à la suite des centaines, le deuxième millier à la suite des deux-centaines, etc.

Les fichiers ne seront donc pas dans l'ordre où ils doivent se présenter pour la séquence, et on perdra beaucoup de temps à les retrier et les renommer à la main, comme on le voit faire souvent !

Pour éviter ce désagrément, il suffit de regarder le nombre total de fichiers que l'on a à traiter et d'en déduire le nombre de caractères que doit comporter le nom de fichier. Si on ne dépasse pas la centaine (maximum 99), alors deux caractères suffiront, si on ne dépasse pas le millier (maximum 999) alors il faudra trois caractères, et ainsi de suite.

Ensuite, il suffira de faire précéder le numéro de fichier par le nombre de zéros nécessaires pour compléter le nombre de caractères utilisés. Par exemple, si on est en dessous du millier, et qu'on attribue les noms sur trois caractères, la numérotation commencera ainsi :
"001", "002", "003", "004", "005", "006", "007", "009", "010", "011" ... etc. Et ils resteront dans l'ordre.
Si on utilise un préfixe ou un suffixe dans le nom de fichier, celui-ci n'interviendra pas sur l'ordre alphabétique puisqu'il s'agit d'une chaîne constante (la même pour tous les fichiers). On dit aussi une expression régulière (par exemple "telleSequence_001", "telleSequence_002", "telleSequence_003", ...).

Toutefois, si on s'est trompé dans le nommage au moment d'une exportation et qu'on a un grand nombre de fichiers à renommer, rien n'est perdu si on pense au service que peut rendre le code : on pourra se faire un petit script utilitaire qui renommera les fichiers ou acteurs correctement et qui replacera ceux qui doivent l'être au bon endroit. Il suffira pour cela de faire un hachage de chacun des noms de fichiers pour en eXtraire, sous forme de valeur numérique, son numéro réel, et de reconcaténer ce numéro avec des caractères qui vont bien (notamment l'insertion de zéros, pour recréer un nom de fichiers correct. On changera ensuite le nom des acteurs en modifiant leur propriété name.

Retour à la sixième étape
Passer à la huitième étape