Classements et approximations▲
Introduction▲
Nous disposons maintenant d'une solide liste de mots et d'un moteur permettant de les relier autant de fois que l'on souhaite, ce qui ouvre l'horizon vers des recherches de corrélation.
Le moteur est brut de décoffrage, avec des liens qui apparaissent dans l'ordre de leur création : un classement automatique serait plus rationnel.
Par ailleurs, les jeux de mots croisés, qui sont notre cible principale, s'accommodent de lettres non accentuées ; les recherches doivent donc porter non seulement sur l'identité d'un mot avec un autre, mais sur leur ressemblance.
Notion floue… que nous allons éclaircir.
Environnement▲
Chapitre 7…
Créons un répertoire Lex7 et recopions dans ce nouveau répertoire tous les fichiers du répertoire Lex6 utilisé précédemment. Pour éviter toute difficulté ultérieure, suivez la check-list :
- ouvrir pLex6.lpi dans Lex7 avec Lazarus ;
- enregistrer uLex6.pas sous le nom de uLex7.pas ;
- accepter la suppression des références à uLex6.pas ;
- enregistrer pLex6.pas sous le nom de pLex7.pas ;
- renommer la fenêtre Form1 : Lex6 devient Lex7 ;
- dans le répertoire Lex7, supprimer les anciens fichiers contenant la mention Lex6.
Nous retrouvons le projet dans l'état où nous l'avions laissé, et les modifications que nous allons effectuer n'affecteront pas l'étape précédente consultable dans le répertoire Lex6.
Onglet Balayage▲
L'onglet Balayage se révèle pratique pour parcourir la liste et, avec son filtre, accéder immédiatement aux mots déjà liés.
L'onglet Édition a l'avantage de faire apparaître la liste des mots liés au mot-titre.
Pour rendre l'interface plus simple, nous remplaçons le Memo de l'onglet Balayage par un ListBox qui affichera les liens.
Dans les propriétés du composant Listbox, nous supprimons les bordures : BorderStyle est basculé sur bsNone.
Nous réduisons la hauteur des deux flèches et plaçons le composant filtre juste au-dessus.
Pour établir un lien visuel entre le mot-titre et ses mots liés, nous allons affecter aux composants Label2 et ListBox2 une même couleur pastel et nous vous proposons la teinte $00C1FFDC (valeur hexa à placer à la ligne Color). Toute autre teinte claire peut convenir.
L'ensemble se présente maintenant comme ceci :
La procédure AffLiens, qui prend en charge l'affichage des liens dans l'onglet Édition, est modifiée pour que le nouveau ListBox soit mis à jour systématiquement :
procedure
TForm1.AffLiens;
var
i, k : integer
;
begin
if
Length(Liens)>0
then
begin
ListBox1.Clear;
k := chercheTab(iMot);
if
k>=0
then
for
i :=1
to
Length(Liens[k]) -1
do
ListBox1.Items.Add(AnsiToUTF8(listeMots[Liens[k, i]]));
ListBox2.Items := ListBox1.Items;
end
;
end
;
Mais l'onglet Balayage doit à son tour être mis à jour si l'utilisateur a l'idée de cliquer sur le composant ListBox : dans l'onglet Événements de ce composant, ligne OnClick, cliquez sur les trois points pour créer la procédure événementielle correspondante :
procedure
TForm1.ListBox2Click(Sender: TObject);
begin
If
ListBox2.ItemIndex >= 0
then
begin
iMot := Liens[chercheTab(iMot), ListBox2.ItemIndex+1
];
MAJBalayage;
end
;
end
;
La première instruction vérifie la présence d'un lien à l'emplacement du clic ; si c'est le cas, la mise à jour de l'onglet Balayage est réalisée.
Lancez l'exécution et essayez… L'ergonomie est déjà plus agréable.
Tri des liens▲
Les liens sont enregistrés dans le tableau principal Lien et dans les tableaux secondaires Liens[i] dans l'ordre de leur création, ce qui ne présente aucune difficulté théorique ou pratique. Le résultat est toutefois peu satisfaisant pour deux raisons :
- au plan esthétique, l'œil s'accommode mal du désordre qui apparaît dans l'affichage des liens ;
- au plan logique, la recherche dans un tas est plus longue que dans une liste ordonnée.
Pour les tableaux secondaires, la solution « épidermique » est simple : il suffit d'affecter à True la propriété Sorted du composant ListBox. Essayez…
L'affichage se fait maintenant dans le bon ordre. Mais…
Si vous cliquez sur un lien, le résultat obtenu est… imprévu. En effet, la mise à jour de l'affichage se fait non pas selon le mot cliqué dans la liste, mais selon sa position.
Et le tri effectué par le composant a modifié sa position…
Il faut donc envisager le tri systématique des tableaux, et le renouveler à chaque création de lien.
Tri des tableaux secondaires▲
La première case restera inchangée, puisque c'est l'identifiant du tableau. Les autres cases seront classées par ordre croissant.
La dernière case est celle qui est affectée en dernier : nous allons donc procéder à une simple remontée jusqu'à ce que la case précédente contienne une valeur inférieure, et nous répéterons l'opération jusqu'à ce qu'aucune permutation n'ait été effectuée.
Pour le tableau Liens[k], nous utiliserons la fonction TriSec(k), qui retournera vrai si aucune permutation n'a été effectuée.
Rappelons qu'une nouvelle fonction ou procédure doit faire d'abord l'objet d'une déclaration avant implémentation (ici on écritfunction TriSec(k : integer) : boolean; suivi d'un appui sur les trois touches Ctrl+Maj+C.
Le code est le suivant(19)Â :
function
TForm1.TriSec(k: integer
): boolean
;
var
i, Tamp : integer
;
begin
TriSec := True
;
for
i := Length(Liens[k]) - 1
downto
2
do
if
Liens[k, i] < Liens[k, i-1
] then
begin
Tamp := Liens[k, i];
Liens[k, i] := Liens[k, i-1
];
Liens[k, i-1
] := Tamp;
TriSec := False
;
end
;
end
;
Notons que la première case (k=0) n'est pas modifiée.
Nous répéterons le balayage jusqu'à ce que la fonction retourne vrai.
Pour lancer le tri de tous les tableaux secondaires, nous plaçons un bouton dans l'onglet Info, et nous lui affectons « TriSec » à la propriété Caption.
Un double clic sur ce nouveau bouton pour créer la procédure Button4Click que l'on peut écrire ainsi :
procedure
TForm1.Button4Click(Sender: TObject);
var
k : integer
;
okTri : boolean
;
begin
for
k := 0
to
Length(Liens)-1
do
if
(Length(Liens[k]) > 2
) then
repeat
okTri := TriSec(k);
until
okTri;
end
;
La variable k balaye successivement tous les tableaux secondaires ; si le tableau considéré contient plus de deux liens, un premier tri est lancé, puis un second, jusqu'à ce qu'aucune permutation n'ait été effectuée (okTri est vrai) ; on passe alors au tableau secondaire suivant.
Exécution : cliquez sur le petit triangle vert et vérifiez que, après action sur le bouton TriSec, tous les tableaux secondaires ont bien été triés.
Si vous quittez le programme en cliquant sur le bouton rouge, le tri est perdu. Si vous actionnez le bouton Arrêt du premier onglet, le tri est conservé.
Tri du tableau principal▲
Nous allons utiliser les mêmes méthodes pour mettre de l'ordre dans le tableau principal, c'est-à -dire ranger les tableaux secondaires dans l'ordre croissant de leur identifiant. La fonction TriPPal peut s'écrire ainsi :
function
TForm1.TriPPal: boolean
;
var
i : integer
;
Tamp : Array
of
integer
;
begin
TriPPal := True
;
for
i := Length(Liens) - 1
downto
1
do
if
Liens[i, 0
] < Liens[i-1
, 0
] then
begin
Tamp := Liens[i];
Liens[i] := Liens[i-1
];
Liens[i-1
] := Tamp;
TriPPal := False
;
end
;
end
;
Pour lancer ce tri, nous ajoutons un bouton dans l'onglet Info, baptisé TriPPal, et dans la procédure onClick de ce composant, nous complétons le code :
procedure
TForm1.Button5Click(Sender: TObject);
var
i : integer
;
begin
for
i := 0
to
Length(Liens) - 1
do
Memo2.Append(listeMots[Liens[i, 0
]]);
repeat
until
TriPPal;
for
i := 0
to
Length(Liens) - 1
do
Memo2.Append(listeMots[Liens[i, 0
]]);
end
;
Les deux premières lignes affichent les mots liés dans l'ordre d'origine ; les deux suivantes font le tri, les deux dernières affichent le résultat.
Pour que le Memo puisse être consulté aisément, n'oubliez pas de porter sa propriété ScrollBar à AutoVertical.
Lancez l'exécution, cliquez sur le bouton TriPPal et appréciez(20)…
Pour la suite du projet, nous supposerons que les tris (secondaire et principal) ont été effectués et mémorisés en quittant le programme par le bouton Arrêt.
Ajout d'un lien▲
Après ajout d'un lien, l'ordre du tableau de liens est probablement détruit. Doit-on lancer le tri général du tableau permis par la fonction TriSec ? Cette fonction impose au moins deux balayages du tableau : un premier pour effectuer les éventuelles permutations, un second pour contrôler que tout est déjà en place.
Mais si le tableau est déjà trié et que le nouveau lien figure en dernière position, il suffit de faire monter sa position jusqu'à ce que l'index qui le précède soit inférieur. Cela remplace deux balayages par un demi… en moyenne… d'où l'intérêt du tri.
La procédure PlaceLiens(k) peut s'écrire ainsi :
procedure
TForm1.PlaceLien(k: integer
);
var
i, Tamp : integer
;
begin
i := Length(Liens[k]) - 1
;
while
(Liens[k, i] < Liens[k, i-1
]) and
(i>1
) do
begin
Tamp := Liens[k, i];
Liens[k, i] := Liens[k, i-1
];
Liens[k, i-1
] := Tamp;
Dec(i);
end
;
end
;
Il faut déclencher ce tri au moment où le nouveau lien est intégré dans le tableau, donc en fin de la procédure Lier, qui devient :
procedure
TForm1.Lier(iMot, iLien: integer
);
var
k : integer
;
begin
k := chercheTab(iMot);
if
k < 0
then
begin
SetLength(Liens, Length(Liens)+1
); //extension du tableau principal
k := Length(Liens)-1
;
SetLength(Liens[k], 1
);
Liens[k][0
] := iMot; //identifiant
Inc(nLiens); //mise à jour du nombre de mots liés
Caption := 'Lex6 '
+IntToStr(nMots)+ ' mots dont '
+IntToStr(nLiens) + ' liés'
;
end
;
SetLength(Liens[k], Length(Liens[k])+1
); //extension du tableau secondaire
Liens[k, Length(Liens[k])-1
] := iLien; // lien
PlaceLien(k); //tri
end
;
Essayez : clic sur le triangle vert, naviguez jusqu'au mot « couleur » (onglet Balayage), ajoutez un lien avec « bleu » (onglet Édition)… Celui-ci est immédiatement placé convenablement.
Avant de quitter (bouton Arrêt), cliquez sur le bouton TriPPal pour que le tableau correspondant au mot « bleu » soit positionné à la bonne place : le Memo vous confirmera que, parmi les mots liés, le mot « bleu » a bien quitté la dernière position.
Création d'un tableau secondaire▲
Lors de la création d'un tableau secondaire (un lien est affecté à un mot qui n'en possédait pas), le nouveau tableau est placé en fin de liste : k:=Length(Liens)-1 (procédure ci-dessus).
Pour donner au nouveau tableau la place qui lui revient, on applique la même méthode que pour le tri des liens, avec une fonction PlaceTab qui retourne la nouvelle position du tableau.
function
TForm1.PlaceTab(k: integer
): integer
;
var
i : integer
;
Tamp : Array
of
integer
;
begin
i := k;
while
(Liens[i, 0
] < Liens[i-1
, 0
]) and
(i>1
) do
begin
Tamp := Liens[i];
Liens[i] := Liens[i-1
];
Liens[i-1
] := Tamp;
dec(i);
end
;
PlaceTab := i;
end
;
La procédure Lier doit donc être à nouveau modifiée pour que la nouvelle position soit prise en compte :
procedure
TForm1.Lier(iMot, iLien: integer
);
var
k : integer
;
begin
k := chercheTab(iMot);
if
k < 0
then
begin
SetLength(Liens, Length(Liens)+1
); //extension du tableau principal
k := Length(Liens)-1
;
SetLength(Liens[k], 1
);
Liens[k][0
] := iMot; //identifiant
k := PlaceTab(k); //nouvel emplacement
Inc(nLiens); //mise à jour du nombre de mots liés
Caption := 'Lex6 '
+IntToStr(nMots)+ ' mots dont '
+IntToStr(nLiens) + ' liés'
;
end
;
SetLength(Liens[k], Length(Liens[k])+1
); //extension du tableau secondaire
Liens[k, Length(Liens[k])-1
] := iLien; // lien
PlaceLien(k); //tri
end
;
Essayez à nouveau en ajoutant au mot « couleur » un lien avec « orange »…
Pensez à quitter en utilisant le bouton du premier onglet.
Le tableau de liens est maintenant ordonné systématiquement, de même que chacun des tableaux secondaires.
Les boutons TriSec et TriPPal sont maintenant inutiles, ainsi que les procédures correspondantes. Nous pouvons les conserver provisoirement, pour rétablir le bon ordre de tous les liens en cas d'incident (coupure de courant, plantage…).
Suppression d'un lien▲
Il peut s'avérer nécessaire de supprimer un lien existant. Cette opération sera traitée dans un onglet dédié, par mesure de prudence.
Dans la fenêtre Lex7, clic droit dans la ligne en grisé, à droite de l'onglet Édition : dans le menu contextuel, choisir Ajouter une page. Dans l'inspecteur d'objets, inscrire « Suppression » dans la propriété Caption.
Dans le nouvel onglet, on ajoute quatre composants :
- un label (propriété Font/Size portée à 14) ;
- un listBox ;
- un second label ;
- et un bouton (propriété Caption portée à « OK »).
Nous désirons que le mot titre et les liens qui apparaissent dans l'onglet Balayage soient répliqués sur notre nouvel onglet ; un clic sur l'un des liens affichera un avertissement dans le second label et activera le bouton.
La procédure MAJSupp peut s'écrire ainsi :
procedure
TForm1.MAJSupp;
begin
Label5.Caption := Label2.Caption;
ListBox4.Items := ListBox2.Items;
Label6.Caption:= ''
;
Button6.Enabled:= False
;
end
;
La procédure MAJBalayage est complétée par l'instruction MAJSupp ;
procedure
TForm1.MAJBalayage;
var
i : integer
;
begin
Label2.Caption:=AnsiToUTF8(listeMots[iMot]);
Label3.Caption:= Label2.Caption;
Label4.Caption:=Label2.Caption;
TrackBar1.Position:= Round(iMot*1000
/nMots);
Edit2.Clear;
AffListe.Clear;
for
i := 0
to
10
do
AffListe.Items.Add(AnsiToUTF8(listeMots[(iMot-5
+ i + nMots) mod
nMots]));
AffListe.Selected[5
] := True
;
AffLiens;
Caption := 'Lex7 '
+IntToStr(nMots)+ ' mots dont '
+IntToStr(nLiens) + ' liés'
;
MAJSupp;
end
;
Pour sélectionner un lien dans l'onglet Suppression, nous utilisons l'événement OnClick :
procedure
TForm1.ListBox4Click(Sender: TObject);
begin
if
ListBox4.ItemIndex >= 0
then
begin
iLien := Liens[chercheTab(iMot), ListBox4.ItemIndex+1
];
Label6.Caption:= 'Supprimer le lien entre '
+
AnsiToUTF8(listeMots[iMot]) + ' et '
+
AnsiToUTF8(listeMots[iLien])+ ' ?'
;
Button6.Enabled := True
;
end
;
end
;
La procédure affecte à la variable iLien l'index du lien à supprimer : il faut déclarer cette variable avant implémentation.
var
Form1: TForm1;
listeMots, listeInfo : TstringList;
iMot, nMots, nLiens, iLien : integer
;
AffListe : TListBox;
Liens : Array
of
Array
of
integer
;
La suppression d'un lien s'accompagne de la réduction de la taille du tableau secondaire, et même de celle du tableau principal si ce lien était le dernier :
procedure
TForm1.SuppLien(iMot, iLien: integer
);
var
i, k : integer
;
begin
//dans le tableau secondaire de iMot, on supprime iLien
k := chercheTab(iMot);
i:=1
;
while
Liens[k, i]<> iLien do
inc(i);
while
(i<Length(Liens[k])-1
) do
begin
Liens[k, i] := Liens[k, i+1
];
inc(i);
end
;
SetLength(Liens[k], Length(Liens[k]) - 1
);//tableau secondaire
if
(Length(Liens[k]) = 1
) then
//le mot n'a plus de lien
begin
for
i := k to
Length(Liens)-2
do
Liens[i] := Liens[i+1
];
SetLength(Liens, Length(Liens)-1
); //tableau principal
nLiens := Length(Liens);
end
;
MAJBalayage;
end
;
Le clic sur le bouton OK lance la suppression dans un sens, puis dans l'autre :
procedure
TForm1.Button6Click(Sender: TObject);
begin
SuppLien(iMot, iLien);
SuppLien(iLien, iMot);
end
;
Un clic sur le petit triangle vert pour essayer les nouvelles fonctions : il est possible maintenant d'ajouter et de retirer à volonté tous les liens que l'on souhaite.
Le bouton Arrêt du premier onglet assure la sauvegarde du résultat.
Mots approchés▲
La pratique un peu laborieuse des tableaux dynamiques nous a éloignés - provisoirement - du sujet principal que constituent les mots croisés. Oublions les majuscules et intéressons-nous aux lettres ou caractères particuliers.
Principes▲
Voici un tableau de 18 caractères, se terminant avec le tiret, l'apostrophe et l'espace :
|à |â|ä|é|è|ê|ë|î|ï|ô|ù|û|ü|ÿ|ç|-|'| |
Dans une grille de mots croisés(21), par convention non écrite, tous sont remplacés par leur homologue dans le tableau suivant(22) :
|a|a|a|e|e|e|e|i|i|o|u|u|u|y|c||||
les trois derniers étant… l'absence de caractère.
Inconsciemment, le joueur remplace tous les caractères du premier tableau par les caractères correspondants du second.
La fonction de recherche que nous avons utilisée précédemment ne s'accommode pas d'un tel laxisme : elle exige l'identité de deux chaînes, caractère par caractère. Il nous faut donc l'enrichir en complétant le signal d'erreur « échec » par l'affichage de mots « approchés » au sens des mots croisés.
Méthode▲
Nous avons déjà vu que la liste de mots était stockée selon le format simplifié à la norme ANSI alors que les affichages sur notre interface graphique exigeaient le format UTF8. Nous avons réglé le problème en adoptant les fonctions de conversion AnsiToUTF8 dans un sens et UTF8ToAnsi de l'autre.
Pour les recherches de mots approchés, il nous faudra comparer des chaînes de caractères débarrassées des lettres accentuées et des majuscules. Le tableau suivant résume les propriétés de nos chaînes selon la phase de leur utilisation :
Propriétés de la chaîne | Stockage | Identité | Approx |
Type | ANSI | ANSI | ANSI |
Présence de majuscules | Oui | Oui | Non |
Affichage interface graphique | UTF8 | UTF8 | UTF8 |
Lettres accentuées | Oui | Oui | Non |
Majuscules accentuées | Non | Non | Non |
Pour des raisons de simplification, nous considérons que les majuscules accentuées ne présentent pas d'intérêt… mais il sera toujours temps d'y revenir si nécessaire.
Les traitements sont au format ANSI et les affichages au format UTF8 : commençons par corriger la procédure onClick qui déclenche la recherche :
procedure
TForm1.Button2Click(Sender: TObject);
begin
Recherche(UTF8ToAnsi(Edit2.Caption));
end
;
Interface graphique▲
Reprenons l'onglet 1 de l'interface, que nous rebaptisons en « Recherche » (propriété Caption).
Nous ajoutons un composant TListBox, avec la propriété Color égale à $00C4F3FF (autre couleur pastel, mais vous pouvez en adopter d'autres). Nous fixons la même couleur au composant Edit2 pour que la liaison entre les deux soit visuellement établie. Après quelques déplacements des composants, nous obtenons une interface qui peut ressembler à ceci :
L'objectif est de permettre la saisie d'un mot dans le composant Edit2, et d'afficher, après consultation de la liste principale :
- le mot identique dans le Label ;
- les mots approchés dans le ListBox.
Élimination des accents▲
La chaîne à rechercher, rMot, est débarrassée de ses majuscules puis balayée du premier caractère au dernier. Si l'un d'entre eux se trouve dans le tableau des lettres accentuées, il est remplacé par le caractère homologue du tableau des lettres non accentuées. La fonction SansAccent peut s'écrire ainsi :
function
TForm1.SansAccent(rMot: string
): string
;
var
i, j : integer
;
cAcc, sAcc : string
;
begin
cAcc := UTF8ToAnsi('à âäéèêëïîôùûüÿç-'' '
);
//regroupe tous les caractères à remplacer
sAcc := 'aaaeeeeiiouuuyc'
;
//regroupe tous les caractères de substitution
SansAccent := ''
;
rMot := LowerCase(rMot);
for
i:=1
to
Length(rMot) do
begin
j := Pos(rMot[i], cAcc);
case
j of
0
: SansAccent := SansAccent + rMot[i];
1
..15
: SansAccent := SansAccent + sAcc[j];
end
;
end
;
end
;
Le recours à cette procédure sera fréquent. Il sera donc judicieux d'envisager la création de constantes globales pour les deux chaînes cAcc et sAcc, ce qui évitera des affectations à répétition.
Balayage de la liste principale▲
La recherche de mots approchés va débuter à partir du premier mot qui commence avec la même première lettre et se terminera lorsque l'on atteindra la première lettre suivante. La comparaison porte sur deux chaînes débarrassées de leurs accents.
Les résultats positifs sont affichés dans le ListBox et les indices stockés dans un tableau d'entiers tabApprox, pour réemploi éventuel. Nous commençons par déclarer cette variable globale avant implémentation :
tabApprox : Array of integer ;
Il s'agit bien d'un tableau dynamique, et il va falloir ajuster sa longueur avant chaque utilisation, mais maintenant nous avons l'habitude…
procedure
TForm1.listeApprox(rechMot: string
);
var
i, k : integer
;
referMot, testMot : string
;
begin
SetLength(tabApprox, 0
); //mise à zéro du tableau
referMot := SansAccent(rechMot); //gabarit de référence
//point de démarrage de la recherche : première lettre du mot
i := 0
;
while
SansAccent(listeMots[i][1
]) < referMot[1
] do
inc(i);
//début de la recherche
repeat
if
SansAccent(listeMots[i]) = referMot then
//pseudo-identité
begin
listBox3.Items.Append(AnsiToUTF8(listeMots[i]));
//enregistrement des index pour réutilisation
SetLength(tabApprox, Length(tabApprox)+1
);
tabApprox[Length(tabApprox)-1
] := i;
end
;
inc(i)
until
(i>nMots-1
) or
(SansAccent(listeMots[i][1
]) > referMot[1
]);
//la première lettre a changé
end
;
Quant à la procédure Recherche, il faut la compléter pour provoquer une recherche complémentaire :
procedure
TForm1.Recherche(rechMot: string
);
var
irechMot : integer
;
begin
listBox3.Clear;
irechMot := listeMots.IndexOf(rechMot);
if
irechMot >= 0
then
begin
Label1.Caption:= AnsiToUTF8(listeMots[irechMot]);
iMot := irechMot;
MAJBalayage;
end
else
begin
Label1.Caption:= 'échec'
;
listeApprox(rechMot);
end
;
end
;
Essai d'exécution avec un clic sur le petit triangle vert.
Dans la zone de saisie, on entre par exemple « macon » et on clique sur le bouton Chercher.
Le résultat « échec » indique qu'aucune correspondance n'a été trouvée.
Par contre, le ListBox affiche les mots approchants : « maçon » et « mâcon ».
Objectif atteint.
Que se passe-t-il si l'on clique sur le mot « maçon » par exemple ? Pour l'instant, rien. Il faut exploiter l'événement onClick du composant(23), et la procédure peut s'écrire comme suit :
procedure
TForm1.ListBox3Click(Sender: TObject);
begin
if
ListBox3.ItemIndex>=0
then
begin
iMot := tabApprox[ListBox3.ItemIndex];
Label1.Caption:=AnsiToUTF8(listeMots[iMot]);
Edit2.Clear;
ListBox3.Clear;
MAJBalayage;
end
;
end
;
Clic sur le triangle vert pour lancer l'exécution.
Et là l'existence de la variable tabApprox montre son intérêt.
Conclusion▲
Nous savons maintenant ranger les listes de liens dans un ordre qui accélère les accès.
Par ailleurs, l'entrée d'un mot nouveau provoque, dans l'onglet Recherche, l'apparition d'une liste de mots approchants. Cette liste constituera un élément décisif lors de l'introduction d'un mot nouveau : c'est ce que nous verrons au prochain chapitre.
Une petite fantaisie pour terminer : voici un dessin exécuté sur smartphone mais pouvant servir d'icône originale à notre logiciel :
Ce n'est pas une œuvre d'art, plutôt un appel aux bonnes volontés !
Il vaut mieux ne pas réfléchir du tout que de ne pas réfléchir assez.
Tristan Bernard.
Le code… L'unité uLex7 se présente maintenant comme suit :
unit
uLex7;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls,
Graphics, Dialogs, StdCtrls, ComCtrls, uDisque;
type
{ TForm1 }
TForm1 = class
(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button5: TButton;
Button6: TButton;
CheckBox1: TCheckBox;
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
ListBox1: TListBox;
ListBox2: TListBox;
ListBox3: TListBox;
ListBox4: TListBox;
Memo2: TMemo;
RadioButton1: TRadioButton;
RadioButton2: TRadioButton;
RadioButton3: TRadioButton;
RadioButton4: TRadioButton;
TabSheet2: TTabSheet;
TabSheet3: TTabSheet;
TabSheet4: TTabSheet;
Zoom: TGroupBox;
Label1: TLabel;
Label2: TLabel;
AffListe: TListBox;
Memo1: TMemo;
PageControl1: TPageControl;
Page1: TTabSheet;
TabSheet1: TTabSheet;
TrackBar1: TTrackBar;
UpDown1: TUpDown;
UpDown2: TUpDown;
procedure
AffListeClick(Sender: TObject);
procedure
Button1Click(Sender: TObject);
procedure
Button2Click(Sender: TObject);
procedure
Button3Click(Sender: TObject);
procedure
Button4Click(Sender: TObject);
procedure
Button5Click(Sender: TObject);
procedure
Button6Click(Sender: TObject);
procedure
CheckBox1Change(Sender: TObject);
procedure
FormCreate(Sender: TObject);
procedure
ListBox1Click(Sender: TObject);
procedure
ListBox2Click(Sender: TObject);
procedure
ListBox3Click(Sender: TObject);
procedure
ListBox4Click(Sender: TObject);
procedure
Recherche(rechMot: string
);
procedure
TrackBar1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer
);
procedure
UpDown1Click(Sender: TObject; Button: TUDBtnType);
procedure
MAJAffichage;
procedure
MAJBalayage;
procedure
UpDown2Click(Sender: TObject; Button: TUDBtnType);
procedure
ZoomMouseLeave(Sender: TObject);
procedure
MAJInfo;
function
chercheTab(iMot : integer
) : integer
;
procedure
Lier(iMot, iLien : integer
);
procedure
AffLiens;
function
TriSec(k : integer
) : boolean
;
function
TriPPal : boolean
;
procedure
PlaceLien(k : integer
);
function
PlaceTab(k : integer
) : integer
;
function
SansAccent(rMot : string
) : string
;
procedure
listeApprox(rechMot : string
);
procedure
MAJSupp;
procedure
SuppLien(iMot, iLien : integer
);
private
{ private declarations }
public
{ public declarations }
end
;
const
delta=5
;
var
Form1: TForm1;
listeMots, listeInfo : TstringList;
iMot, nMots, nLiens, iLien : integer
;
AffListe : TListBox;
Liens : Array
of
Array
of
integer
;
tabApprox : Array
of
integer
; //index des mots approchants
implementation
{$R *.lfm}
{ TForm1 }
procedure
TForm1.Button1Click(Sender: TObject);
begin
regLiens;
listeMots.Free;
Application.Terminate;
end
;
procedure
TForm1.AffListeClick(Sender: TObject);
begin
iMot := (iMot -delta + AffListe.ItemIndex + nMots) mod
nMots;
MAJBalayage;
end
;
procedure
TForm1.Button2Click(Sender: TObject);
begin
Recherche(UTF8ToAnsi(Edit2.Caption));
end
;
procedure
TForm1.Button3Click(Sender: TObject);
var
iLien, k : integer
;
begin
iLien := listeMots.IndexOf(UTF8ToAnsi(Edit3.Caption));
if
iLien<0
then
ShowMessage('Ce mot n''existe pas, recommencez'
)
else
begin
Lier(iMot, iLien);
Lier(iLien, iMot);
AffLiens;
end
;
Edit3.Clear;
end
;
procedure
TForm1.Button4Click(Sender: TObject);
var
k : integer
;
okTri : boolean
;
begin
for
k := 0
to
Length(Liens)-1
do
if
(Length(Liens[k]) > 2
) then
repeat
okTri := TriSec(k);
until
okTri;
end
;
procedure
TForm1.Button5Click(Sender: TObject);
var
i : integer
;
begin
for
i := 0
to
Length(Liens) - 1
do
Memo2.Append(listeMots[Liens[i, 0
]]);
repeat
until
TriPPal;
for
i := 0
to
Length(Liens) - 1
do
Memo2.Append(listeMots[Liens[i, 0
]]);
end
;
procedure
TForm1.Button6Click(Sender: TObject);
begin
SuppLien(iMot, iLien);
SuppLien(iLien, iMot);
end
;
procedure
TForm1.CheckBox1Change(Sender: TObject);
begin
if
CheckBox1.Checked then
CheckBox1.Caption := 'Avec filtre'
else
CheckBox1.Caption := 'Sans filtre'
;
end
;
procedure
TForm1.FormCreate(Sender: TObject);
begin
listeMots := TStringList.Create;
LireFichier(listeMots);
nMots := listeMots.Count;
Memo1.Append('Premier mot : '
+listeMots[0
]);
Memo1.Append('Dernier mot : '
+listeMots[nMots-1
]);
lireLiens;
iMot := 0
;
SetLength(tabApprox, 0
);
Edit3.Clear;
MAJAffichage;
MAJBalayage;
end
;
procedure
TForm1.ListBox1Click(Sender: TObject);
begin
If
ListBox1.ItemIndex >= 0
then
begin
iMot := Liens[chercheTab(iMot), ListBox1.ItemIndex+1
];
Label4.Caption := AnsiToUTF8(listeMots[iMot]);
AffLiens;
end
;
end
;
procedure
TForm1.ListBox2Click(Sender: TObject); //couleur $00C1FFDC
begin
If
ListBox2.ItemIndex >= 0
then
begin
iMot := Liens[chercheTab(iMot), ListBox2.ItemIndex+1
];
MAJBalayage;
end
;
end
;
procedure
TForm1.ListBox3Click(Sender: TObject);
begin
if
ListBox3.ItemIndex>=0
then
begin
iMot := tabApprox[ListBox3.ItemIndex];
Label1.Caption:=AnsiToUTF8(listeMots[iMot]);
Edit2.Clear;
ListBox3.Clear;
MAJBalayage;
end
;
end
;
procedure
TForm1.ListBox4Click(Sender: TObject);
begin
if
ListBox4.ItemIndex >= 0
then
begin
iLien := Liens[chercheTab(iMot), ListBox4.ItemIndex+1
];
Label6.Caption:= 'Supprimer le lien entre '
+
AnsiToUTF8(listeMots[iMot]) + ' et '
+
AnsiToUTF8(listeMots[iLien])+ ' ?'
;
Button6.Enabled := True
;
end
;
end
;
procedure
TForm1.MAJBalayage;
var
i : integer
;
begin
Label2.Caption:=AnsiToUTF8(listeMots[iMot]);
Label3.Caption:= Label2.Caption;
Label4.Caption:=Label2.Caption;
TrackBar1.Position:= Round(iMot*1000
/nMots);
Edit2.Clear;
AffListe.Clear;
for
i := 0
to
10
do
AffListe.Items.Add(AnsiToUTF8(listeMots[(iMot-5
+ i + nMots) mod
nMots]));
AffListe.Selected[5
] := True
;
AffLiens;
Caption := 'Lex7 '
+IntToStr(nMots)+ ' mots dont '
+IntToStr(nLiens) + ' liés'
;
MAJSupp;
end
;
procedure
TForm1.UpDown2Click(Sender: TObject; Button: TUDBtnType);
begin
if
CheckBox1.Checked then
repeat
inc(iMot);
until
(chercheTab(iMot)>=0
) or
(iMot=nMots)
else
if
Button=btNext then
Inc(iMot, UpDown2.Increment)
else
Dec(iMot, UpDown2.Increment);
iMot := (iMot + nMots) mod
(nMots);
MAJBalayage;
end
;
procedure
TForm1.ZoomMouseLeave(Sender: TObject);
begin
if
RadioButton1.Checked then
UpDown2.Increment := 1
else
if
RadioButton2.Checked then
UpDown2.Increment := 10
else
if
RadioButton3.Checked then
UpDown2.Increment := 100
else
if
RadioButton4.Checked then
UpDown2.Increment := 1000
;
end
;
procedure
TForm1.MAJInfo;
begin
end
;
function
TForm1.chercheTab(iMot: integer
): integer
;
var
i : integer
;
begin
chercheTab := -1
;
i := 0
;
while
(i<Length(Liens)) and
(Liens[i, 0
]<>iMot)do
inc(i);
if
i<Length(Liens) then
chercheTab := i;
end
;
procedure
TForm1.Lier(iMot, iLien: integer
);
var
k : integer
;
begin
k := chercheTab(iMot);
if
k < 0
then
begin
SetLength(Liens, Length(Liens)+1
); //extension du tableau principal
k := Length(Liens)-1
;
SetLength(Liens[k], 1
);
Liens[k][0
] := iMot; //identifiant
k := PlaceTab(k); //nouvel emplacement
Inc(nLiens); //mise à jour du nombre de mots liés
Caption := 'Lex6 '
+IntToStr(nMots)+ ' mots dont '
+IntToStr(nLiens) + ' liés'
;
end
;
SetLength(Liens[k], Length(Liens[k])+1
); //extension du tableau secondaire
Liens[k, Length(Liens[k])-1
] := iLien; // lien
PlaceLien(k); //tri
end
;
procedure
TForm1.AffLiens;
var
i, k : integer
;
begin
if
Length(Liens)>0
then
begin
ListBox1.Clear;
k := chercheTab(iMot);
if
k>=0
then
for
i :=1
to
Length(Liens[k]) -1
do
ListBox1.Items.Add(AnsiToUTF8(listeMots[Liens[k, i]]));
ListBox2.Items := ListBox1.Items;
end
;
end
;
function
TForm1.TriSec(k: integer
): boolean
;
var
i, Tamp : integer
;
begin
TriSec := True
;
for
i := Length(Liens[k]) - 1
downto
2
do
if
Liens[k, i] < Liens[k, i-1
] then
begin
Tamp := Liens[k, i];
Liens[k, i] := Liens[k, i-1
];
Liens[k, i-1
] := Tamp;
TriSec := False
;
end
;
end
;
function
TForm1.TriPPal: boolean
;
var
i : integer
;
Tamp : Array
of
integer
;
begin
TriPPal := True
;
for
i := Length(Liens) - 1
downto
1
do
if
Liens[i, 0
] < Liens[i-1
, 0
] then
begin
Tamp := Liens[i];
Liens[i] := Liens[i-1
];
Liens[i-1
] := Tamp;
TriPPal := False
;
end
;
end
;
procedure
TForm1.PlaceLien(k: integer
);
var
i, Tamp : integer
;
begin
i := Length(Liens[k]) - 1
;
while
(Liens[k, i] < Liens[k, i-1
]) and
(i>1
) do
begin
Tamp := Liens[k, i];
Liens[k, i] := Liens[k, i-1
];
Liens[k, i-1
] := Tamp;
Dec(i);
end
;
end
;
function
TForm1.PlaceTab(k: integer
): integer
;
var
i : integer
;
Tamp : Array
of
integer
;
begin
i := k;
while
(Liens[i, 0
] < Liens[i-1
, 0
]) and
(i>1
) do
begin
Tamp := Liens[i];
Liens[i] := Liens[i-1
];
Liens[i-1
] := Tamp;
dec(i);
end
;
PlaceTab := i;
end
;
function
TForm1.SansAccent(rMot: string
): string
;
var
i, j : integer
;
cAcc, sAcc : string
;
begin
cAcc := UTF8ToAnsi('à âäéèêëïîôùûüÿç-'' '
);
//regroupe tous les caractères à remplacer
sAcc := 'aaaeeeeiiouuuyc'
;
//regroupe tous les caractères de substitution
SansAccent := ''
;
rMot := LowerCase(rMot);
for
i:=1
to
Length(rMot) do
begin
j := Pos(rMot[i], cAcc);
case
j of
0
: SansAccent := SansAccent + rMot[i];
1
..15
: SansAccent := SansAccent + sAcc[j];
end
;
end
;
end
;
procedure
TForm1.listeApprox(rechMot: string
);
var
i, k : integer
;
referMot, testMot : string
;
begin
SetLength(tabApprox, 0
); //mise à zéro du tableau
referMot := SansAccent(rechMot);
if
referMot>''
then
begin
//point de démarrage de la recherche : première lettre du mot
i := 0
;
while
SansAccent(listeMots[i][1
]) < referMot[1
] do
inc(i);
//début de la recherche
repeat
if
SansAccent(listeMots[i]) = referMot then
begin
listBox3.Items.Append(AnsiToUTF8(listeMots[i]));
//enregistrement des index pour réutilisation
SetLength(tabApprox, Length(tabApprox)+1
);
tabApprox[Length(tabApprox)-1
] := i;
end
;
inc(i)
until
(i>nMots-1
) or
(SansAccent(listeMots[i][1
]) > referMot[1
]);
//la première lettre a changé
end
;
end
;
procedure
TForm1.MAJSupp;
begin
Label5.Caption := Label2.Caption;
ListBox4.Items := ListBox2.Items;
Label6.Caption:= ''
;
Button6.Enabled:= False
;
end
;
procedure
TForm1.SuppLien(iMot, iLien: integer
);
var
i, k : integer
;
begin
//dans le tableau secondaire de iMot, on supprime iLien
k := chercheTab(iMot);
i:=1
;
while
Liens[k, i]<> iLien do
inc(i);
while
(i<Length(Liens[k])-1
) do
begin
Liens[k, i] := Liens[k, i+1
];
inc(i);
end
;
SetLength(Liens[k], Length(Liens[k]) - 1
);
if
(Length(Liens[k]) = 1
) then
//le mot n'a plus de lien
begin
for
i := k to
Length(Liens)-2
do
Liens[i] := Liens[i+1
];
SetLength(Liens, Length(Liens)-1
);
nLiens := Length(Liens);
end
;
MAJBalayage;
end
;
procedure
TForm1.TrackBar1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer
);
begin
iMot := Round(TrackBar1.Position*nMots/1000
);
iMot := (iMot + nMots) mod
nMots;
MAJBalayage;
end
;
procedure
TForm1.Recherche(rechMot: string
);
var
irechMot : integer
;
begin
listBox3.Clear;
irechMot := listeMots.IndexOf(rechMot);
if
irechMot >= 0
then
begin
Label1.Caption:= AnsiToUTF8(listeMots[irechMot]);
iMot := irechMot;
MAJBalayage;
end
else
begin
Label1.Caption:= 'échec'
;
listeApprox(rechMot);
end
;
end
;
procedure
TForm1.UpDown1Click(Sender: TObject; Button: TUDBtnType);
begin
if
Button=btNext then
Inc(iMot)
else
Dec(iMot);
iMot := iMot + nMots mod
nMots;
MAJAffichage;
end
;
procedure
TForm1.MAJAffichage;
begin
Label1.Caption:=AnsiToUTF8(listeMots[iMot]);
Edit2.Caption:= ''
;
Memo1.Append('Index '
+IntToStr(iMot));
end
;
end
.