Pour faire une recherche phonétique, on utilise souvent ce qu’on appelle une transcription soundex des mots. C’est une réécriture du mot, dans un alphabet restreint et sur un nombre de caractères restreint également. La plupart des algorithmes qu’on peut trouver sur internet sont conçus uniquement à la langue anglaise. Pour utiliser la recherche phonétique en français, il faut donc adapter cet algorithme.
L’implémentation française la plus courante utilise l’algorithme décrit par Frédéric Brouard. Malheureusement cet algorithme ne me satisfait pas vraiment, car il n’est finalement pas très adapté à langue française.
En effet cet algorithme a pas mal de défauts :
- il est très aléatoire dans le cas de dédoublement de consonnes. Par exemple, il ne permet de trouver que bananne (soundex
BNN
) est une approximation de banane (soundexBN
) - il est très peu sélectif sur les voyelles. Par exemple poulpe et palpa ont le même soundex (
PLP
) alors que pour moi, poulpe et palpa sont deux mots éloignés, malgré leur consonnes communes. Par contre, il ne reconnaîtra pas Aymeric (soundexAYMR
) comme étant un homophone de Emeric (soundexEMRC
)… - il n’est pas vraiment capable de distinguer les conjugaisons. Par exemple palper (soundex
PLPR
) et palpé (soundexPLP
) sont éloignés l’un de l’autre.
J’ai donc réalisé une nouvelle implémentation de soundex
qui est plus adaptée (à mon sens) à la langue française. Je n’ai évidemment pas pour ambition de réaliser une solution parfaite (ce qui est impossible à faire à partir d’un système automatisé sans dictionnaire).
Algorithme
Mon algorithme, comme je l’ai déjà dit, est une adaptation de celui de Frédéric Brouard. Il se compose de 2 étapes principales :
1 le préformatage, qui consiste à transformer le chaîne de caractère brute, en une chaîne analysable. 1 l’analyse de la chaîne, et la recherche de entités phoniques élémentaires qui la composent
Préformatage
Le préformatage est extrêmement simple :
1 On converti la chaîne en majuscule 1 On converti chaque caractère accentué vers son caractère non-accentué correspondant 1 On filtre pour ne conserver que les lettres (de A à Z)
Ainsi après ce premier filtrage, Mac-Cartney devient MACCARTNEY et palpé sera PALPE
Traitement des données
Le traitement des données consiste à reconnaître les sonorités. Pour ceci on utilise la table de conversion suivante qui associe à chaque sonorité complexe un caractère :
Combinaison | Caractère |
---|---|
G (prononcé GUE), C (prononcé Q), CK, K, QU, Q | K |
CH, SH, SCH | 9 |
ST | T |
PF (en début de mot), PH | F |
Z (en fin de mot) | ZE |
Z, ZZ, C (prononcé SE) | S |
G (prononcé JE) | J |
EAU, AU | O |
IN, UN, AIN, EIN (proncés UN) | 1 |
AON, AOM, EN, AN (prononcés EN) | A |
EY, EI, AY, AI, OE, OEU, EU, ER | E |
OI | O |
ILLE, I | Y |
OU, OW | U |
ON, OM | O |
KN (en début de mot) | N |
Une fois qu’on a cette table de correspondances (elle n’est peut-être pas exhaustive… je n’hésiterais pas à l’améliorer), on applique toutes les modifications, on supprime les lettres qui sont présentes en doublon et finalement on supprime les H restants. Il faut également supprimer les caractères muets en fin de mot. En français, ce seront les X, T, D, S (et le L qui les précède si il existe) ou les E.
Ensuite, il faut faire un nettoyage sur la chaîne de caractère. Ce nettoyage consiste à rechercher les phonèmes importants et à supprimer les caractères muets ou peu audibles : on veut identifier ce qui fait la particularité d’une syllabe. Frédéric Brouard considère que toutes les voyelles (exceptés les Y précédés d’une voyelle) placées autre part qu’en début de mot sont insignifiantes… ça me paraît le gros point faible de sa méthode. Personnellement j’ai essayé d’identifier une liste de sonorité dominantes. Voici celles que j’ai actuellement :
Sonorité | Informations |
---|---|
K, T, P | Ce sont les consonnes qui sont très marquées, qui rythment le mot |
A, U, O, 1 | Ce sont les son qu’on entend le mieux, contrairement aux I ou aux E qui mettent plus en avant les consonnes. Le A est à mon avis particulier, car il est faible mais suffisamment marquant pour effacer des consonnes comme le R |
Y | Y n’est marquant que lorsqu’il a un rôle de consonne : quand il permet de lier deux parties du mot, comme dans ”voyelle”, où il lie le ”vo” et le ”elle”. Il est donc important lorsqu’il est encadré par une ou des voyelles. Dans les autres cas, c’est une voyelle faible |
L | Contrairement aux K, T et P, le L permet d’adoucir le mot, en particulier lorsqu’il sert de liaison entre une consonne et une voyelle. Il est donc important de le conserver |
Ces choix sont relativement arbitraires. Le comportement de l’algorithme est alors simple :
- Si une voyelle est liée à une consonne de liaison (qui fait le lien entre la voyelle et une autre consonne, c’est souvent le cas du L ou du R, on exclut de ce cas les consommes fortes)
- Si en plus il s’agit d’une voyelle forte
- alors, on supprime la consonne de liaison et on garde la voyelle
Ensuite, on supprime les voyelles faibles (sauf si elles commencent le mot). Pour cette suppression, on considère le A comme une voyelle faible.
Pour terminer on coupe la chaîne obtenue à 4 caractères (mais il est parfaitement envisageable de prendre un soundex sur plus (ou moins) de caractères. Simplement, dans la plupart des cas, 4 caractères suffisent amplement à avoir un mot significatif.
Implémentation
Mon implémentation en PHP est téléchargeable ici. Elle est testable plus bas dans cette même page. Voici le code de la fonction soundex_fr (je ne garantis pas que le code disponible sur cette page reste constamment à jour, contrairement au lien de téléchargement) :$$L’algorithme prend en compte les modifications fournies par les lecteurs et dont vous retrouverez le détail dans les commentaires. Merci donc aux commentateurs, et particulièrement à David$$
function soundex_fr($sIn)
{
static $convVIn, $convVOut, $convGuIn, $convGuOut, $accents;
if (!isset($convGuIn)) {
$accents = array('É' => 'E', 'È' => 'E', 'Ë' => 'E', 'Ê' => 'E',
'Á' => 'A', 'À' => 'A', 'Ä' => 'A', 'Â' => 'A', 'Å' => 'A', 'Ã' => 'A',
'Ï' => 'I', 'Î' => 'I', 'Ì' => 'I', 'Í' => 'I',
'Ô' => 'O', 'Ö' => 'O', 'Ò' => 'O', 'Ó' => 'O', 'Õ' => 'O', 'Ø' => 'O',
'Ú' => 'U', 'Ù' => 'U', 'Û' => 'U', 'Ü' => 'U',
'Ç' => 'C', 'Ñ' => 'N', 'Ç' => 'S', '¿' => 'E',
'é' => 'e', 'è' => 'e', 'ë' => 'e', 'ê' => 'e',
'á' => 'a', 'à' => 'a', 'ä' => 'a', 'â' => 'a', 'å' => 'a', 'ã' => 'a',
'ï' => 'i', 'î' => 'i', 'ì' => 'i', 'í' => 'i',
'ô' => 'o', 'ö' => 'o', 'ò' => 'o', 'ó' => 'o', 'õ' => 'o', 'ø' => 'o',
'ú' => 'u', 'ù' => 'u', 'û' => 'u', 'ü' => 'u',
'ç' => 'c', 'ñ' => 'n');
$convGuIn = array( 'GUI', 'GUE', 'GA', 'GO', 'GU', 'SCI', 'SCE', 'SC', 'CA', 'CO',
'CU', 'QU', 'Q', 'CC', 'CK', 'G', 'ST', 'PH');
$convGuOut = array( 'KI', 'KE', 'KA', 'KO', 'K', 'SI', 'SE', 'SK', 'KA', 'KO',
'KU', 'K', 'K', 'K', 'K', 'J', 'T', 'F');
$convVIn = array( '/E?(AU)/', '/([EA])?[UI]([NM])([^EAIOUY]|$)/', '/[AE]O?[NM]([^AEIOUY]|$)/',
'/[EA][IY]([NM]?[^NM]|$)/', '/(^|[^OEUIA])(OEU|OE|EU)([^OEUIA]|$)/', '/OI/',
'/(ILLE?|I)/', '/O(U|W)/', '/O[NM]($|[^EAOUIY])/', '/(SC|S|C)H/',
'/([^AEIOUY1])[^AEIOUYLKTPNR]([UAO])([^AEIOUY])/', '/([^AEIOUY]|^)([AUO])[^AEIOUYLKTP]([^AEIOUY1])/', '/^KN/',
'/^PF/', '/C([^AEIOUY]|$)/', '/E(Z|R)$/',
'/C/', '/Z$/', '/(?<!^)Z+/', '/H/', '/W/');
$convVOut = array( 'O', '1\\3', 'A\\1',
'E\\1', '\\1E\\3', 'O',
'Y', 'U', 'O\\1', '9',-
'\\1\\2\\3', '\\1\\2\\3', 'N',
'F', 'K\\1', 'E',
'S', 'SE', 'S', '', 'V');
}
// Si il n'y a pas de mot, on sort immédiatement
if ( $sIn === '' ) return ' ';
// On supprime les accents-
$sIn = strtr( $sIn, $accents);
// On met tout en minuscule-
$sIn = strtoupper( $sIn );
// On supprime tout ce qui n'est pas une lettre
$sIn = preg_replace( '`[^A-Z]`', '', $sIn );
// Si la chaîne ne fait qu'un seul caractère, on sort avec.
if ( strlen( $sIn ) === 1 ) return $sIn . ' ';
// on remplace les consonnances primaires
$sIn = str_replace( $convGuIn, $convGuOut, $sIn );
// on supprime les lettres répétitives
$sIn = preg_replace( '`(.)\\1`', '$1', $sIn );
// on réinterprète les voyelles
$sIn = preg_replace( $convVIn, $convVOut, $sIn);
// on supprime les terminaisons T, D, S, X (et le L qui précède si existe)
$sIn = preg_replace( '`L?[TDX]?S?$`', '', $sIn );
// on supprime les E, A et Y qui ne sont pas en première position
$sIn = preg_replace( '`(?!^)Y([^AEOU]|$)`', '\\1', $sIn);
$sIn = preg_replace( '`(?!^)[EA]`', '', $sIn);
return substr( $sIn . ' ', 0, 4);
}
Tests
Avec cette implémentation, on a par exemple :
Mot | Soundex | Mot | Soundex |
---|---|---|---|
Aymeric | EMRK | Emeric | EMRK |
Banane | BNN | Bananne | BNN |
Palper | PLP | Palpé | PLP |
Palpa | PLP | Poulpe | PULP |
Mario | MRYO | Marion | MRYO |
On a par contre des résultats moyens lorsque des H séparent des voyelles faibles : par exemple Mouahaha donne MU (contre M avec la version précédente).
~test~
N’hésitez pas à faire des tests et à me faire part des résultats qui semblent anormaux.
Conclusion
Cette version est loin d’être parfaite, mais elle est plus adaptée au français que celle de Frédéric Brouard. Elle n’est d’ailleurs pas terminée et risque d’évoluer dans un futur proche, en particuliers pour ce qui est des tables de transcriptions et de prédominances.