Dans de nombreuses situations, on se retrouve avec du texte entré qui ne correspond pas à l'exact résultats attendu, en raison de fautes de frappe ou d'orthographe. Avec perl, on peut envisager plusieurs types d'approches pour parvenir à établir une sorte de tolérance textuelle.
La première idée, la plus évidente, est de faire appel au module Text::Soundex qui est une implémentation de l'algorithme de Donald E. Knuth et qui a le grand avantage de faire partie de la distribution standard de perl. La fonction soundex(); retourne une valeur phonétique pour le mot proposé sous la forme d'une chaîne de quatre carractères (une lettre majuscule et trois chiffres). Deux mots phonétiquement équivalents retourneront la même valeur, deux mots proches, des valeurs proches. Le seul problème, et il est de taille, est que ce module est de conception anglo-saxone, ce qui implique une approche différente de la proximité phonétique par rapport au français. Je ne connais pas à ce jour de modules proposant une alternative francophone. Ce module peut tout de même être utilisé dans de nombreuses situations pour voir si le mot entré trouve des occurences semblables, à l'intérieur d'un thésaurus index ou dictionaire.
Un concurrent de Soundex est le module Text::Metaphone. Plus moderne et aussi basé sur l'anglais, ce module donne accès à la fonction Metaphone qui prend pour argument une chaîne et qui retourne son équivalent phonétique sous la forme d'une autre chaîne de carractères, spéciaux ceux-ci car phonétiques. Lent, il produit un résultat plus riche que soundex, qui peut convenir dans certains cas, mais qui est moins manipulable dans la mesure où son rendu n'est pas formaté strictement mais contextuel. Théoriquement, le module Text::TransMetaphone doit fournir une internationalisation du processus d'encodage phonétique mais il reste limité à un nombre limité de langues telles que l'arabe, le grec, l'hêbreu, le russe ou encore le japonnais pour ne citer que les plus importants. Peu de langues européeennes donc pour le moment mais le projet est ambitieux et tous les espoirs sont potentiellement permis.
Une deuxième approche, plus centrée sur les fautes de frappe consiste à évaluer le taux/nombre de paires de lettres concordantes par rapport au total. On procède par paires et non par lettre pour simplifier les calculs de pertinance. Ce peut être implémenté de la sorte, avec deux chaînes $a et $b en convenant que la première est la plus longue :
$nbfault = 0;
for $i (0..length($a)-2) {
$suba = substr($a,$i,2);
$nbfault++ unless (($suba eq substr($b,$i,2)) or ($suba eq substr($b,$i-1,2)));
}
print $nbfault;
On obtient ainsi 1 en cas de doublon, et 2 dans le cas d'une lettre en plus ou d'une substitution. Ce code est prévu pour des erreurs d'une seule lettre mais peut bien évidement être étendu. Pour le cas fréquent des lettres inversées, il suffit d'ajouter la condition ($suba eq reverse(substr($b,$i,2))) pour obtenir un total de 2 au lieu de 3 si l'erreur est en plein mot. Au total, avec un résultat inférieur ou égal à 2, on peut conclure à la faute de frappe avec l'avantage d'être indifférent à la langue utilisée.
Si l'on compare les deux méthodes par un petit test :
use Benchmark;
$timer = new Benchmark;
for $i (1..10000) {
...
}
$timez = new Benchmark;
$td = timediff($timer,$timez);
print timestr($td);
On constate que le soundex est beaucoup plus consommateur de temps processeur dans un rapport d'environ 5 à 1 (variable selon les platteformes) ce qui est loin d'être négligeable mais son objectif est autrement plus ambitieux.
Le module String::Approx apporte lui aussi son lot de solutions. Ce module est défini conme permettant le "fuzzy matching" ce qui peut être compris comme la correspondance approximative. A la base il mesure la "Levenshtein edit distance", c'est à dire qu'il évalue le nombre d'erreurs qu'il rencontre. Il reconnaît trois type d'erreurs: insertions, suppressions et substitutions. Son fonctionnement interne est de mesurer le nombre d'opérations qu'il faut effectuer pour aller d'un mot à un autre. Si l'on se contente d'un niveau d'erreur de 1, alors on peut s'en remettre à la fonction amatch qui se contente d'un taux d'erreur de 10% par défaut.
Avec $oj contenant notre mot d'origine et @base contenant la liste des mots possibles, pour obtenir @possibl contenant les mots possibles, on peut coder ainsi :
use String::Approx qw(amatch); @possibl = amatch($oj,@base);
Utilisée en contexte scalaire, amatch renvoie le nombre d'erreurs. Cependant, malgré les améliorations qu'il a subit récement, il reste beaucoup plus lent que notre approche.
Il peut aussi être très profitable dans des cas extrêmes de combiner le soundex avec une recherche des similitudes car deux mots phonétiquement proches retournent des codes qui varient en général d'un seul carractère :
use Text:Soundex;
$vala = soundex($a);
$valb = soundex($b);
$total = 0;
for $i (0..3) {
$total++ if (substr($vala,$i,1) eq substr($valb,$i,1));
}
print "OK" if ($total>2);
Mais dans ce cas, on parvient souvent à un niveau de lattitude qui est peut-être trop grand, parfois au delà des différences phonétiques. Ce type de manipulations n'est pas aisément possible avec Text::Metaphone.
PS: Si vous vous posez des questions sur la tolerance textuelle en perl, contentez-vous de noms de variables explicites et d'un bon use strict pour éviter toute erreur ou alors utilisez le module Symbol::Approx::Sub à vos propres risques.