Recherche logique▲
Introduction▲
L'outil Masque développé dans le chapitre précédent présente une utilité certaine pour le joueur de mots croisés qui connaît une ou des lettres du mot à trouver.
Dans le cas contraire, le joueur ne dispose que de la définition de ce mot, définition souvent piégée par les ambiguïtés de la langue.
Dans ce chapitre, nous allons aborder cet aspect culturel ou humoristique du vocabulaire en utilisant les liens déjà disponibles.
Et nous pourrons vérifier que… ça marche !
Environnement▲
Chapitre 10…
Créons un répertoire Lex10 et recopions dans ce nouveau répertoire tous les fichiers du répertoire Lex9 utilisé précédemment. Pour éviter toute difficulté ultérieure, suivez la check-list :
- ouvrir pLex9.lpi dans Lex10 avec Lazarus ;
- enregistrer uLex9.pas sous le nom de uLex10.pas ;
- accepter la suppression des références à uLex9.pas ;
- enregistrer pLex9.pas sous le nom de pLex10.pas ;
- renommer la fenêtre Lex9 en Lex10 ;
- dans le répertoire Lex10, supprimer les anciens fichiers contenant la mention Lex9 ;
- dans la procédure MAJBalayage, remplacer Lex9 par Lex10.
Les fichiers de données (mots et liens) peuvent être téléchargés à l'aide du lien inclus dans le chapitre 9.
Nous retrouvons le projet dans l'état où nous l'avions laissé, et les modifications que nous allons apporter n'affecteront pas l'étape précédente consultable dans le répertoire Lex9.
Humour▲
La recherche d'un mot peut s'effectuer dans l'onglet Balayage : le curseur permet de parcourir aisément notre liste de mots par un simple déplacement de la souris ; les flèches droite et gauche autorisent un déplacement d'une unité, ou davantage en jouant avec le zoom.
La zone de saisie - dans le même onglet - et un clic sur le bouton  (flèche vers le haut) permettent de découvrir si le mot saisi existe ou non, avec affichage de mots proches éventuels.
La recherche peut aussi s'effectuer à partir de l'onglet Masque, qui permet de préciser la longueur du mot et la position des lettres connues.
Recherches élémentaires, mon cher Watson…
En effet, les jeux de mots croisés apportent des informations plus complexes dans le cadre de « définitions » souvent ambiguës où l'humour sous-jacent révèle le talent de l'auteur… mais seulement quand la solution apparaît !
C'est là que les liens dont nous avons entamé la construction vont montrer leur intérêt.
Interface graphique▲
L'onglet Logique est encore vide.
Pour faciliter la compréhension du code, nous allons prendre la peine, dans ce chapitre, de renommer l'onglet, ainsi que les composants que nous y insérerons. Chacun pourra apprécier ainsi l'intérêt de cette méthode qui reste facultative.
Pour Lazarus, l'onglet Logique apparaît sous le nom générique de TabSheet7(36). Dans sa propriété Name, nous remplaçons ce nom par tabLogique.
Ensuite, nous insérons cinq composants que nous renommons et définissons ainsi :
- un label, renommé en labReq, caption porté à « Requête », Font/Size portée à 14 ;
- un bouton, renommé en boutFH, caption porté(37) à « ⇪ » qui correspond au code utf8 $E2$87$AA ; taille fixée à 14 ; largeur (Width) ramenée à 25 ;
- un edit, renommé edReq, code couleur $00BBFFFD ;
- un second bouton, renommé boutReq ; caption porté à « Chercher », largeur 100 ;
- un ListBox, renommé lisReq, même couleur de fond que edReq.
L'ensemble est remanié à l'aide de la souris pour obtenir sensiblement ceci :
Le choix des teintes est éminemment subjectif, et chacun peut évidemment apporter sa touche personnelle.
En ce qui concerne l'espace vide à droite de l'onglet, nous le réservons pour un usage ultérieur.
Méthode▲
Notre idée est d'afficher tous les mots liés aux mots saisis.
Deux aspects sont à prendre en compte : la fréquence des occurrences et la profondeur de la recherche.
Fréquence▲
La première fois qu'un mot apparaît dans les liens, nous notons sa référence (indice du mot ou position dans la liste principale) et complétons mentalement cette note par le chiffre 1 qui indique que c'est la première fois qu'il est enregistré.
La seconde fois, la fréquence sera incrémentée de une unité, ainsi de suite.
Ainsi, les liens obtenus peuvent être affichés par ordre de fréquence décroissante, de façon à orienter en priorité l'utilisateur vers la ou les réponses les plus pertinentes. C'est cette méthode qu'ont retenue les moteurs de recherche habituels.
En ce qui nous concerne, l'intérêt de la fréquence est limité dans la mesure où une définition de mot croisé fait appel à une notion accessoire, un sens secondaire : c'est précisément ce côté subalterne, minoritaire, que nous devons saisir. Nous ne tiendrons donc pas compte ici du paramètre fréquence.
Les plus courageux pourront néanmoins créer la variable globale
tabOccur : Array of Array[0..1] of integer;
La première partie de chaque élément (tabOccur[i,0]) recevra l'indice du mot, et la seconde partie (tabOccur[i,1]) recevra la fréquence.
La variable est un tableau dynamique… qu'il faudra initialiser.
Profondeur▲
Le niveau 1 de la recherche consiste à établir la liste des mots directement liés aux mots-clés de départ : cette liste contient normalement des mots nouveaux qui orientent l'utilisateur vers des horizons différents.
Que se passe-t-il si la recherche est relancée à partir de ce stade ? Les horizons nouveaux ouvrent de nouvelles perspectives, etc.
Mais il est évident qu'un nombre trop élevé de recherches successives aboutirait à un brouillage contre-productif.
Avec une entrée de deux mots-clés, nous obtenons, par exemple, les nombres d'occurrences suivants(38) :
Profondeur | 0 | 1 | 2 | 3 |
N. occurrences | 2 | 4 | 33 | 119 |
Nous nous limiterons ici à un niveau 2, c'est-à -dire que nous réutiliserons une fois les résultats obtenus après la première recherche.
Comme la notion de fréquence n'a pas été retenue, nous disposerons ainsi d'une liste totalement indifférenciée(39) dans laquelle l'utilisateur pourra faire son choix.
Variables▲
Chaque mot saisi est versé, après un clic sur le bouton , dans la ligne de requête. Nous stockerons les indices (places de chaque mot dans la liste principale) dans une variable constituée par un tableau dynamique d'entiers.
Parmi les variables globales disponibles, nous trouvons déjà la variable tabApprox, qui correspond à un tableau dynamique d'entiers. Nous ajoutons la variable tabReq qui recevra les indices de nos requêtes.
Pour les réponses, nous avons besoin d'un autre tableau dynamique qui stockera l'indice de ces mots. Nous ajoutons dans la liste des variables globales la variable tabOccur :
var
Form1: TForm1;
listeMots, listeInfo : TstringList;
iMot, nMots, nLiens, iLien : integer
;
AffListe : TListBox;
Liens : Array
of
Array
of
integer
;
tabApprox, tabReq, tabOccur : Array
of
integer
;
sAcc, cAcc : string
;
Nous avons déjà rencontré les tableaux dynamiques : ils doivent faire l'objet d'une initialisation.
Initialisation▲
L'onglet Logique doit être préparé pour la première saisie dès que l'utilisateur aura décidé de l'afficher.
Nous utiliserons donc la procédure OnShow de l'onglet.
Pour cela, nous sélectionnons l'onglet dans l'inspecteur d'objets (ici la ligne TabSheet7) et au-dessous, dans l'onglet Événements, nous cliquons sur les trois points qui terminent la ligne OnShow.
Le curseur clignote dans l'éditeur de source entre les mots begin et end de la procédure TabSheet7Show que nous complétons ainsi :
procedure
TForm1.tabLogiqueShow(Sender: TObject);
begin
Edit1.Clear;
ListBox5.Clear;
Label3.Caption:= 'Requête '
;
SetLength(tabReq, 0
);
SetLength(tabOccur, 0
);
end
;
Les composants sont nettoyés, les deux tableaux dynamiques initialisés.
Logiquement, nous procédons de même pour l'événement OnExit :
procedure
TForm1.tabLogiqueExit(Sender: TObject);
begin
SetLength(tabReq, 0
);
SetLength(tabOccur, 0
);
end
;
De cette façon, nos deux variables globales auront entièrement libéré l'espace mémoire lorsque l'utilisateur quittera l'onglet.
Requête▲
L'entrée d'un mot se fait naturellement par la zone de saisie et le clic sur le bouton . Si le mot existe, il bascule dans la ligne de requête et son indice est stocké dans la table tabReq :
procedure
TForm1.boutFHClick(Sender: TObject);
var
i, j, k : integer
;
begin
//ajoute un mot à la liste de base tabReq
i := listeMots.IndexOf(UTF8ToAnsi(Edit1.Caption));
if
i>=0
then
//le mot existe dans la liste principale
begin
j:=0
;
k := Length(tabReq);
while
(j<k) and
(i<>tabReq[j]) do
inc(j);
if
j=k then
//le mot peut être ajouté
begin
SetLength(tabReq, k+1
); //ajoute une unité au tableau
tabReq[k] := i;
//affichage de la requête dans la ligne titre
if
labReq.Caption = 'Requête '
then
labReq.Caption:= edReq.Caption //premier mot-clé
else
labReq.Caption:= labReq.Caption +' + '
+edReq.Caption;
end
;
end
;
edReq.Clear;
end
;
Un premier contrôle vérifie que le mot existe déjà dans la liste principale ; le second évite de saisir un doublon qui alourdirait inutilement la requête.
Au premier affichage, le mot saisi remplace le mot « Requête ».
La requête peut être complétée à volonté : chaque nouveau mot est ajouté au précédent, séparé par le signe +.
Recherche▲
Nous avons fixé la profondeur de recherche à 2 : ce sera la valeur de la constante profRech déclarée en premier.
La variable j balaye le tableau des liens jusqu'à l'indice du mot-clé : si celui-ci dispose de liens, ils sont stockés dans la variable tabOccur par l'intermédiaire de la procédure(40) AjoutOccur, qui s'assure de ne pas enregistrer de doublons :
procedure
TForm1.AjoutOccur(iOccur: integer
);
var
i : integer
;
begin
//vérifier que cette occurrence n'a pas encore été rencontrée
i:=0
;
while
(i<Length(tabOccur)) and
(iOccur<> tabOccur[i]) do
inc(i);
if
i= Length(tabOccur) then
//première occurrence du mot
begin
SetLength(tabOccur, i+1
);
tabOccur[i] := iOccur;
end
;
end
;
Un clic sur le bouton Chercher déclenche pour commencer une vérification sur la longueur de la requête, qui ne doit pas être vide.
À la fin de la première recherche, les occurrences obtenues sont basculées dans la variable tabReq, qui constituera la nouvelle base de la recherche ; le tableau tabOccur est remis à zéro au début de la seconde recherche(41).
procedure
TForm1.boutReqClick(Sender: TObject);
const
profRech = 2
; //fixe la profondeur de la recherche
var
i, j, k, n : integer
;
begin
if
Length(tabReq)>0
then
//la requête ne doit pas être vide
begin
for
n := 1
to
profRech do
begin
SetLength(tabOccur, 0
);
for
i:=0
to
Length(tabReq)-1
do
begin
j := 0
;
while
(j<Length(Liens)) and
(Liens[j, 0
]<>tabReq[i])do
inc(j);
if
j<Length(Liens) then
for
k:=1
to
Length(Liens[j])-1
do
AjoutOccur(Liens[j, k]);
end
;
tabReq := tabOccur;
end
;
j := Length(tabOccur);
//afficher les occurrences et leur nombre
lisReq.Items.Append(IntToStr(j)+' occurrences : '
);
TriOccur; //trier avant affichage
for
i:=0
to
j-1
do
lisReq.Items.Append(AnsiToUTF8(listeMots[tabOccur[i]]));
end
;
end
;
En fin de recherche, les mots trouvés sont affichés avec, en tête, l'indication de leur nombre. Pour apporter un peu de clarté, les mots sont triés par la procédure TriOccur. Nous avons déjà vu comment déclarer et créer une nouvelle procédure.
procedure
TForm1.TriOccur;
var
i, j, occTampon : integer
;
triOK : boolean
;
begin
i := 0
;
j := Length(tabOccur)-2
;
triOK := True
;
repeat
if
(tabOccur[i] > tabOccur[i+1
]) then
begin
occTampon := tabOccur[i];
tabOccur[i] := tabOccur[i+1
];
tabOccur[i+1
] := occTampon;T
triOK := False
;
end
;
inc(i);
if
not
triOK and
(i>j) then
begin
i:=0
; //nouvelle passe de permutations
triOK:= True
;
end
;
until
triOK and
(i>j);
end
;
Application▲
Nous allons utiliser nos nouveaux outils.
Dans le premier chapitre, nous avions évoqué l'énigme présentée par Tristan Bernard : « vide les baignoires et remplit les lavabos ». Les deux mots-clés sont à l'évidence « baignoire » et « lavabo » : nous les entrons l'un après l'autre à l'aide du bouton .
Un clic sur le bouton Chercher nous donne ceci :
À ce stade, il est bien difficile de conclure à la réussite ou à l'échec de notre requête…
Complément d'information▲
Le cruciverbiste dispose, en plus de la définition d'un mot, d'une grille qui indique le nombre de lettres de ce mot, et précise, s'il a de la chance, quelques lettres déjà obtenues par croisement.
Nous revenons ici au principe de l'outil Masque, qui est déjà disponible dans l'onglet précédent. Nous pourrions dupliquer la liste des réponses dans cet onglet, et laisser l'initiative à l'utilisateur.
Il nous a semblé plus ergonomique de dupliquer l'onglet Masque dans notre onglet Logique.
Nous ajoutons donc quatre composants supplémentaires que nous renommons et arrangeons ainsi :
- Le Label7, renommé labMasq, rappelle qu'il faut saisir les lettres connues séparées par le signe « $ » ;
- un Edit, renommé edMasq, qui recevra les indications de l'utilisateur, couleur $00F4ECFF ;
- un bouton renommé boutMasq, Caption « Masque », qui déclenche une procédure directement inspirée de celle qui a été mise en œuvre dans l'onglet précédent, la différence portant sur le champ de recherche : ici, la liste de mots est limitée aux occurrences déjà affichées ;
- un listBox, renommé lisMasq, couleur $00F4ECFF.
Le code peut s'écrire ainsi :
procedure
TForm1.boutMasqClick(Sender: TObject);
var
i, j, k : integer
;
motCour, sMasque : string
;
begin
i := 0
;
sMasque := edMasq.Caption;
k := Length(sMasque);
lisMasq.Clear;
repeat
motCour := SansAccent(listeMots[tabOccur[i]]);
if
Length(motCour) = k then
//balayage du mot pour comparaison des lettres
begin
j := 1
;
while
(j<=k) and
((sMasque[j]='$'
) or
(sMasque[j] = motCour[j])) do
inc(j);
if
j>k then
lisMasq.Items.Append(AnsiToUTF8(listeMots[tabOccur[i]]));
end
;
inc(i);
until
(i=Length(tabOccur));
end
;
Pour en simplifier l'utilisation, nous cachons les outils de masque tant que la requête n'est pas effectuée. La procédure onShow de l'onglet doit donc faire l'objet d'un complément :
procedure
TForm1.tabLogiqueShow(Sender: TObject);
begin
edReq.Clear;
lisReq.Clear;
labReq.Caption:= 'Requête '
;
labMasq.Caption := 'saisir les lettres séparées'
+#13
+' par le signe $'
;
boutMasq.Enabled:=False
;
edMasq.Clear;
edMasq.Visible:=False
;
labMasq.Visible:=False
;
lisMasq.Visible:=False
;
SetLength(tabReq, 0
);
SetLength(tabOccur, 0
);
end
;
Mais en fin de requête, les outils doivent apparaître : la procédure Button5Click est modifiée ainsi :
procedure
TForm1.boutReqClick(Sender: TObject);
const
profRech = 2
; //fixe la profondeur de la recherche
var
i, j, k, n : integer
;
begin
if
Length(tabReq)>0
then
//la requête ne doit pas être vide
begin
for
n := 1
to
profRech do
begin
SetLength(tabOccur, 0
);
for
i:=0
to
Length(tabReq)-1
do
begin
j := 0
;
while
(j<Length(Liens)) and
(Liens[j, 0
]<>tabReq[i])do
inc(j);
if
j<Length(Liens) then
for
k:=1
to
Length(Liens[j])-1
do
AjoutOccur(Liens[j, k]);
end
;
tabReq := tabOccur;
end
;
j := Length(tabOccur);
//afficher les occurrences et leur nombre
lisReq.Items.Append(IntToStr(j)+' occurrences : '
);
TriOccur; //trier avant affichage
for
i:=0
to
j-1
do
lisReq.Items.Append(AnsiToUTF8(listeMots[tabOccur[i]]));
end
;
edMasq.Clear;
edMasq.Visible:=True
;
labMasq.Visible:=True
;
lisMasq.Visible:=True
;
boutMasq.Enabled:=True
;
end
;
Pour notre énigme, nous connaissons au minimum la longueur de la réponse, soit huit lettres.
Nous rédigeons à nouveau la requête « baignoire + lavabo », précisons la longueur du mot, soit 8 fois le signe « $ » dans le TEdit, et nous obtenons ceci après un clic sur le bouton Masque :
La bonne réponse est bien sûr l'entracte, qui vide les baignoires et remplit les lavabos !
Interprétation▲
Une telle requête présentée sur les moteurs de recherche habituels aboutit à des résultats… décourageants.
Par exemple, avec Google, nous obtenons 3 700 000 réponses en 0,27 seconde, mais ce sont des informations essentiellement commerciales, donc sans intérêt dans les circonstances qui nous intéressent.
Or nous venons d'obtenir deux réponses(42), dont une correcte, ce qui constitue, a priori, une performance hautement improbable. Notre application mérite donc une petite analyse critique.
Le mécanisme des sélections et des tris que nous avons mis en œuvre est incontestable : il peut être réutilisé et donnera toujours les mêmes résultats.
Voyons alors comment s'est opérée la saisie des liens.
Liens du premier niveau▲
Un mot-clé est « baignoire » : si ce mot est entré dans l'onglet Liens, notre logiciel affiche les résultats suivants :
L'autre mot-clé est « lavabo ». L'affichage donne ceci :
Au total, le premier niveau de recherche propose quatre mots, ce qui est bien maigre. La répétition du mot « toilette » n'apporte aucune information supplémentaire, puisque le paramètre fréquence n'entre pas en jeu.
Deuxième niveau▲
Que se passe-t-il lors de la recherche du second niveau ? Nous voyons déjà que le mot « baignoire » est associé au mot « théâtre » (une baignoire est une sorte de loge). Les liens disponibles sur ce mot sont nombreux(43) :
Et là , nous voyons apparaître, comme par magie, le mot « entracte » qui est précisément le mot cherché !
Deux recherches successives ont suffi, mais il n'est pas exclu qu'il faille aller plus loin dans certains cas.
Les indices manquants sont fournis sur la grille (longueur du mot) et, éventuellement par les lettres déjà identifiées sur la grille.
Conclusion▲
Avec seulement 5 000 mots liés, notre base de données est déjà capable de fournir des résultats intéressants. Et au fur et à mesure que ce nombre augmentera, les résultats seront de plus en plus pertinents : ainsi, chacun pourra se constituer sa base personnelle… et en faire profiter ses amis !
Notre projet est maintenant terminé, l'objectif est atteint.
Nous verrons, dans le prochain chapitre, quelques améliorations techniques telles que la fusion des fichiers de mots et de liens et la création d'une fenêtre de sélection. Ensuite pourront être abordés les sujets les plus fréquemment évoqués par les amateurs de mots croisés et… de Lazarus.
Amour : Mot en cinq lettres, trois voyelles, deux consonnes et deux idiots .
Ambrose Bierce
Le code de l'unité uDisque n'a pas changé.
Celui de l'unité uLex10 se présente maintenant ainsi :
unit
ulex10;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls,
Graphics, Dialogs, StdCtrls, ComCtrls, uDisque;
type
{ TForm1 }
TForm1 = class
(TForm)
Button1: TButton;
Button10: TButton;
Button2: TButton;
Button3: TButton;
boutFH: TButton;
boutReq: TButton;
boutMasq: TButton;
Button6: TButton;
Button7: TButton;
Button8: TButton;
Button9: TButton;
CheckBox1: TCheckBox;
edMasq: TEdit;
edReq: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
labMasq: TLabel;
labReq: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
ListBox1: TListBox;
ListBox2: TListBox;
ListBox3: TListBox;
ListBox4: TListBox;
lisReq: TListBox;
lisMasq: TListBox;
RadioButton1: TRadioButton;
RadioButton2: TRadioButton;
RadioButton3: TRadioButton;
RadioButton4: TRadioButton;
TabSheet3: TTabSheet;
TabSheet4: TTabSheet;
TabSheet5: TTabSheet;
TabSheet6: TTabSheet;
tabLogique: TTabSheet;
Zoom: TGroupBox;
Label1: TLabel;
Label2: TLabel;
AffListe: TListBox;
PageControl1: TPageControl;
Page1: TTabSheet;
TabSheet1: TTabSheet;
TrackBar1: TTrackBar;
UpDown2: TUpDown;
procedure
AffListeClick(Sender: TObject);
procedure
boutFHClick(Sender: TObject);
procedure
boutMasqClick(Sender: TObject);
procedure
boutReqClick(Sender: TObject);
procedure
Button10Click(Sender: TObject);
procedure
Button1Click(Sender: TObject);
procedure
Button2Click(Sender: TObject);
procedure
Button3Click(Sender: TObject);
procedure
Button6Click(Sender: TObject);
procedure
Button7Click(Sender: TObject);
procedure
Button8Click(Sender: TObject);
procedure
Button9Click(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
tabLogiqueExit(Sender: TObject);
procedure
tabLogiqueShow(Sender: TObject);
procedure
TabSheet1Show(Sender: TObject);
procedure
TabSheet3Show(Sender: TObject);
procedure
TabSheet4Show(Sender: TObject);
procedure
TabSheet5Show(Sender: TObject);
procedure
TrackBar1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer
);
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;
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
);
function
fMotNouv(rechMot : string
) : integer
;
function
fAnnonceMotNouveau(motNouv : string
) : integer
;
procedure
AjoutMot(motNouv : string
; indexMotNouv : integer
);
procedure
AjoutMotSecur(motNouv : string
; indexMotNouv : integer
);
procedure
SupMot(motCour : string
; indexMotCour : integer
);
function
fDoublon(iMot, iLien: integer
): boolean
;
procedure
Masque(rechMot: string
);
procedure
AjoutOccur(iOccur : integer
);
procedure
TriOccur;
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, tabReq, tabOccur : Array
of
integer
; //index des mots approchants ⇪ utf8 $E2$87$AA flèche haut
sAcc, cAcc : string
;
implementation
{$R *.lfm}
{ TForm1 }
procedure
TForm1.Button1Click(Sender: TObject);
begin
regLiens;
regFichier(listeMots);
listeMots.Free;
Application.Terminate;
end
;
procedure
TForm1.AffListeClick(Sender: TObject);
begin
iMot := (iMot -delta + AffListe.ItemIndex + nMots) mod
nMots;
MAJBalayage;
end
;
procedure
TForm1.boutFHClick(Sender: TObject);
var
i, j, k : integer
;
begin
//ajoute un mot à la liste de base tabReq
i := listeMots.IndexOf(UTF8ToAnsi(edReq.Caption));
if
i>=0
then
//le mot existe dans la liste principale
begin
j:=0
;
k := Length(tabReq);
while
(j<k) and
(i<>tabReq[j]) do
inc(j);
if
j=k then
//le mot peut être ajouté
begin
SetLength(tabReq, k+1
); //ajoute une unité au tableau
tabReq[k] := i;
//affichage de la requête dans la ligne titre
if
labReq.Caption = 'Requête '
then
labReq.Caption:= edReq.Caption //premier mot-clé
else
labReq.Caption:= labReq.Caption +' + '
+edReq.Caption;
end
;
end
;
edReq.Clear;
end
;
procedure
TForm1.boutMasqClick(Sender: TObject);
var
i, j, k : integer
;
motCour, sMasque : string
;
begin
i := 0
;
sMasque := edMasq.Caption;
k := Length(sMasque);
lisMasq.Clear;
repeat
motCour := SansAccent(listeMots[tabOccur[i]]);
if
Length(motCour) = k then
//balayage du mot pour comparaison des lettres
begin
j := 1
;
while
(j<=k) and
((sMasque[j]='$'
) or
(sMasque[j] = motCour[j])) do
inc(j);
if
j>k then
lisMasq.Items.Append(AnsiToUTF8(listeMots[tabOccur[i]]));
end
;
inc(i);
until
(i=Length(tabOccur));
end
;
procedure
TForm1.boutReqClick(Sender: TObject);
const
profRech = 2
; //fixe la profondeur de la recherche
var
i, j, k, n : integer
;
begin
if
Length(tabReq)>0
then
//la requête ne doit pas être vide
begin
for
n := 1
to
profRech do
begin
SetLength(tabOccur, 0
);
for
i:=0
to
Length(tabReq)-1
do
begin
j := 0
;
while
(j<Length(Liens)) and
(Liens[j, 0
]<>tabReq[i])do
inc(j);
if
j<Length(Liens) then
for
k:=1
to
Length(Liens[j])-1
do
AjoutOccur(Liens[j, k]);
end
;
tabReq := tabOccur;
end
;
j := Length(tabOccur);
//afficher les occurrences et leur nombre
lisReq.Items.Append(IntToStr(j)+' occurrences : '
);
TriOccur; //trier avant affichage
for
i:=0
to
j-1
do
lisReq.Items.Append(AnsiToUTF8(listeMots[tabOccur[i]]));
end
;
edMasq.Clear;
edMasq.Visible:=True
;
labMasq.Visible:=True
;
lisMasq.Visible:=True
;
boutMasq.Enabled:=True
;
end
;
procedure
TForm1.Button10Click(Sender: TObject);
var
iLien : integer
;
motNouv : string
;
begin
if
Edit3.Caption > #47
then
begin
motNouv := UTF8ToAnsi(Edit3.Caption);
iLien := listeMots.IndexOf(motNouv);
if
iLien<0
then
iLien := fAnnonceMotNouveau(motNouv);
if
(iLien>=0
) and
(iLien<>iMot) and
not
fDoublon(iMot, iLien) then
begin
Label4.Caption:= Edit3.Caption;
iMot := iLien;
end
;
AffLiens;
end
else
ShowMessage('Entrez le mot'
);
Edit3.Clear;
end
;
procedure
TForm1.Button2Click(Sender: TObject);
begin
//Recherche(UTF8ToAnsi(Edit2.Caption));
ListBox3.Clear;
Masque(SansAccent(UTF8ToAnsi(Edit2.Caption)));
end
;
function
TForm1.fMotNouv(rechMot: string
): integer
;
var
i, k : integer
;
referMot : string
;
begin
SetLength(tabApprox, 0
); //mise à zéro du tableau
referMot := SansAccent(rechMot);
//point de démarrage de la recherche : première lettre du mot
i := 0
;
k := 0
;
while
SansAccent(listeMots[i][1
]) < referMot[1
] do
inc(i);
//début de la recherche
repeat
//k index futur du mot nouveau
if
(k=0
) and
(SansAccent(listeMots[i]) > referMot) then
k := i
else
if
SansAccent(listeMots[i]) = referMot then
begin
//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
]);
if
k=0
then
fMotNouv := nMots else
fMotNouv := k;
end
;
//==========================
function
TForm1.fAnnonceMotNouveau(motNouv: string
): integer
;
var
i, indexMotNouv : integer
;
Rep : string
;
begin
if
listeMots.IndexOf(motNouv)<0
then
begin
indexMotNouv := fMotNouv(motNouv);
Rep := 'Ajouter le mot '
+ AnsiToUTF8(motNouv) +'Â ? '
;
if
Length(tabApprox)>0
then
//affichage des mots proches éventuels
begin
Rep := Rep +#13#10
+ 'Mots existants : '
;
For
i:=0
to
Length(tabApprox) - 1
do
Rep := Rep + ANSIToUTF8(listeMots[tabApprox[i]])+' /'
;
end
;
if
MessageDlg ('Mot nouveau'
, Rep, mtConfirmation,
[mbYes, mbNo],0
) = mrYes
then
begin
AjoutMot(motNouv, indexMotNouv);
fAnnonceMotNouveau := indexMotNouv;
end
else
fAnnonceMotNouveau := -1
;
end
;
end
;
procedure
TForm1.AjoutMot(motNouv: string
; indexMotNouv: integer
);
var
nouvLiens : array
of
array
of
integer
;
i, j : integer
;
begin
nouvLiens := Liens;
for
i:=0
to
Length(Liens)-1
do
for
j:=0
to
Length(Liens[i])-1
do
if
Liens[i, j]>= indexMotNouv then
inc(Liens[i, j]);
if
iMot >= indexMotNouv then
inc(iMot); //mise à jour effectuée
SetLength(nouvLiens, 0
);
listeMots.Insert(indexMotNouv, motNouv); //modification de la liste principale
inc(nMots);
end
;
//===========doublons=======================
function
TForm1.fDoublon(iMot, iLien: integer
): boolean
;
var
i, j : integer
;
begin
//vérifie que le lien n'existe pas déjÃ
//cherche le tableau de liens correspondant à iMot
fDoublon := False
;
j := chercheTab(iMot);
if
j>=0
then
begin
i := Length(Liens[j])-1
;
while
(i>0
) and
(iLien<Liens[j, i]) do
dec(i);
if
iLien = Liens[j, i] then
fDoublon := True
;
if
fDoublon then
ShowMessage('Doublon refusé'
);
end
;
end
;
procedure
TForm1.Masque(rechMot: string
);
var
i, j, k : integer
;
motCour : string
;
begin
i := 0
;
k := Length(rechMot);
Label1.Caption:= ('Mot de '
+ IntToStr(k)+ ' lettres'
);
ListBox3.Clear;
repeat
motCour := SansAccent(listeMots[i]);
if
Length(motCour) = k then
//balayage du mot pour comparaison des lettres
begin
j := 1
;
while
(j<=k) and
((rechMot[j]='$'
) or
(rechMot[j] = motCour[j])) do
inc(j);
if
j>k then
ListBox3.Items.Append(AnsiToUTF8(listeMots[i]));
end
;
inc(i);
until
(i=nMots) or
((rechMot[1
]='$'
) and
(rechMot[1
] > motCour[1
]));
//balayage tant que la première lettre est inférieure à celle du masque
if
ListBox3.Count=0
then
Label1.Caption:= ('échec'
)
else
Label1.Caption:= (IntToStr(ListBox3.Count)+
' mots trouvés'
);
end
;
procedure
TForm1.AjoutOccur(iOccur: integer
);
var
i : integer
;
begin
//vérifier que cette occurrence n'a pas encore été rencontrée
i:=0
;
while
(i<Length(tabOccur)) and
(iOccur<> tabOccur[i]) do
inc(i);
if
i= Length(tabOccur) then
//première occurrence du mot
begin
SetLength(tabOccur, i+1
);
tabOccur[i] := iOccur;
end
;
end
;
procedure
TForm1.TriOccur;
var
i, j, occTampon : integer
;
triOK : boolean
;
begin
i := 0
;
j := Length(tabOccur)-2
;
triOK := True
;
repeat
if
(tabOccur[i] > tabOccur[i+1
]) then
begin
occTampon := tabOccur[i];
tabOccur[i] := tabOccur[i+1
];
tabOccur[i+1
] := occTampon;
triOK := False
;
end
;
inc(i);
if
not
triOK and
(i>j) then
begin
i:=0
; //nouvelle passe de permutations
triOK:= True
;
end
;
until
triOK and
(i>j);
end
;
procedure
TForm1.AjoutMotSecur(motNouv: string
; indexMotNouv: integer
);
var
nouvLiens : array
of
array
of
integer
;
i, j : integer
;
begin
SetLength(nouvLiens, Length(Liens));
for
i:=0
to
Length(Liens)-1
do
begin
SetLength(nouvLiens[i], Length(Liens[i]));
for
j:=0
to
Length(Liens[i])-1
do
if
Liens[i, j]>= indexMotNouv then
nouvLiens[i, j] := Liens[i, j] + 1
else
nouvLiens[i, j] := Liens[i, j];
end
;
Liens := nouvLiens; //mise à jour effectuée
if
iMot >= indexMotNouv then
inc(iMot);
listeMots.Insert(indexMotNouv, motNouv); //modification de la liste principale
inc(nMots);
end
;
//==============================
procedure
TForm1.SupMot(motCour: string
; indexMotCour: integer
);
var
nouvLiens : array
of
array
of
integer
;
i, j : integer
;
begin
SetLength(nouvLiens, Length(Liens));
for
i:=0
to
Length(nouvLiens)-1
do
begin
SetLength(nouvLiens[i], Length(Liens[i]));
for
j:=0
to
Length(nouvLiens[i])-1
do
if
Liens[i, j]>= indexMotCour then
nouvLiens[i, j] := Liens[i, j] - 1
else
nouvLiens[i, j] := Liens[i, j];
end
;
if
iMot >= indexMotCour then
dec(iMot);
Liens := nouvLiens;
//mise à jour effectuée
listeMots.Delete(indexMotCour); //modification de la liste principale
dec(nMots);
end
;
procedure
TForm1.Button3Click(Sender: TObject);
var
iLien : integer
;
motNouv : string
;
begin
if
Edit3.Caption > #47
then
begin
motNouv := UTF8ToAnsi(Edit3.Caption);
iLien := listeMots.IndexOf(motNouv);
if
iLien<0
then
iLien := fAnnonceMotNouveau(motNouv);
if
(iLien>=0
) and
(iLien<>iMot) and
not
fDoublon(iMot, iLien) then
begin
Lier(iMot, iLien);
Lier(iLien, iMot);
end
;
AffLiens;
end
else
ShowMessage('Entrez le mot'
);
Edit3.Clear;
end
;
procedure
TForm1.Button6Click(Sender: TObject);
begin
SuppLien(iMot, iLien);
SuppLien(iLien, iMot);
end
;
//=================
procedure
TForm1.Button7Click(Sender: TObject);
var
i : integer
;
motNouv : string
;
begin
motNouv := UTF8ToAnsi(Edit4.Caption);
i := listeMots.IndexOf(motNouv);
if
(i >= 0
) then
ShowMessage('Mot existant'
)
else
begin
i := fAnnonceMotNouveau(motNouv);
if
i>=0
then
iMot := i;
Edit4.Clear;
end
;
end
;
procedure
TForm1.Button8Click(Sender: TObject);
var
indexMotCour : integer
;
motCour, Rep : string
;
begin
//le mot peut être supprimé si aucun lien ne lui est affecté
motCour := UTF8ToAnsi(Edit4.Caption);
indexMotCour := listeMots.IndexOf(motCour);
if
(indexMotCour<0
) then
ShowMessage ('Supprimé'
)
else
if
(chercheTab(indexMotCour)<0
) then
begin
Rep := 'Supprimer le mot '
+ Edit4.Caption +' ? '
;
if
MessageDlg ('Suppression'
, Rep, mtConfirmation,
[mbYes, mbNo],0
) = mrYes
then
SupMot(motCour, indexMotCour);
MAJBalayage;
Edit4.Clear;
end
else
ShowMessage ('Supprimez les liens avant de supprimer le mot'
);
end
;
procedure
TForm1.Button9Click(Sender: TObject);
begin
ShowMessage('Supprimez le mot, puis ajoutez le nouveau mot'
);
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;
listeMots.CaseSensitive:=True
;
LireFichier(listeMots);
nMots := listeMots.Count;
lireLiens;
iMot := 0
;
Edit3.Clear;
Edit2.Clear;
Label1.Caption := 'saisir les lettres séparées par le signe $'
;
MAJBalayage;
cAcc := UTF8ToAnsi('à âäéèêëïîôùûüÿç-'' '
);
//regroupe tous les caractères à remplacer
sAcc := 'aaaeeeeiiouuuyc'
;
//regroupe tous les caractères de substitution
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 := listeMots.IndexOf(ListBox3.Items[ListBox3.ItemIndex]);
Label1.Caption:= listeMots[iMot];
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]);
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 := 'Lex10 '
+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
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
;
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
;
//les variables sAcc et cAcc sont créées au démarrage
begin
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 : integer
;
referMot : 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 := Label4.Caption;
ListBox4.Items := ListBox1.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.tabLogiqueExit(Sender: TObject);
begin
SetLength(tabReq, 0
);
SetLength(tabOccur, 0
);
end
;
procedure
TForm1.tabLogiqueShow(Sender: TObject);
begin
edReq.Clear;
lisReq.Clear;
labReq.Caption:= 'Requête '
;
labMasq.Caption := 'saisir les lettres séparées'
+#13
+' par le signe $'
;
boutMasq.Enabled:=False
;
edMasq.Clear;
edMasq.Visible:=False
;
labMasq.Visible:=False
;
lisMasq.Visible:=False
;
SetLength(tabReq, 0
);
SetLength(tabOccur, 0
);
end
;
procedure
TForm1.TabSheet1Show(Sender: TObject);
begin
MAJBalayage;
end
;
procedure
TForm1.TabSheet3Show(Sender: TObject);
begin
MAJBalayage;
end
;
procedure
TForm1.TabSheet4Show(Sender: TObject);
begin
MAJSupp;
end
;
procedure
TForm1.TabSheet5Show(Sender: TObject);
begin
Edit4.Caption := AnsiToUTF8(listeMots[iMot]);
end
;
end
.