Voilà le stemmer dont je parlais, il s'agit du stemmer "Paice/Husk". L'auteur original du script est en commentaires, je me suis contenté de faire un refactoring pour le passer en POO, et de compléter un peu la liste des règles FR (c'est là qu'est le gros du travail, qui n'est je pense jamais totalement terminé).
Note importante : j'ai écrit cette classe pour un site que j'ai repris et qui avait des problèmes assez bizarres d'encodage que je n'avais pas le temps de régler. Il y a donc quelques bidouilles de charset qui mériteraient d'être éclaircies pour une utilisation globale.
Pour l'utiliser c'est assez simple :
- Inclure "Stemmer_FR" ou "Stemmer_EN" (selon la langue à utiliser) ou s'assurer qu'ils seront autochargés
- Include "Stemmer" ou s'assurer qu'il sera autochargé
- Instancier l'objet avec $stemmer = Stemmer::factory('fr') ou Stemmer::factory('en')
- Appeler $stemmer->textStemIndex($monTexte) pour un texte
- On peut aussi appeler simplement $stemmer->stem($monMot) pour un simple mot
La class principale (note: dans la version que j'utilise réellement il y a également tout un set de fonctions destinées à ezPdo, afin de rendre automatique et persistante l'indexation de tout objet héritant de Stemmer, je vous en fais grâce) :
<?php
/**
* Paice/Husk Stemmer
* This code is in the public domain.
* @author original author : Alexis Ulrich (http://alx2002.free.fr)
* @author oop refactoring : Nicolas Chambrier ([email protected])
*/
if (!function_exists('mb_strrev')) {
/**
* mb-string version of strrev
*
* @param string $string
* @param string $charset
* @return string
*/
function mb_strrev($string, $charset = null) {
if ($charset === null) {
$charset = mb_detect_encoding($string);
}
if ($charset == 'UTF-8') {
$string = utf8_decode($string);
$string = strrev($string);
$string = utf8_encode($string);
} else {
$string = strrev($string);
}
return $string;
}
}
if (!function_exists('mb_html_entity_decode')) {
/**
* mb-string version of html_entity_decode
*
* @param string $string
* @param string $charset
* @return string
*/
function mb_html_entity_decode($string, $charset = null) {
if ($charset === null) {
$charset = mb_detect_encoding($string);
}
if ($charset == 'UTF-8') {
$string = html_entity_decode($string);
$string = utf8_encode($string);
} else {
$string = html_entity_decode($string);
}
return $string;
}
}
/**
* Main class
*
* @see Stemmer::factory()
*/
class Stemmer {
protected $rulePattern = '/^([a-z]*)(\*){0,1}(\d)([a-z]*)([.|>])/';
protected $rules = array('end0.');
protected $stopWords = array();
protected $vowels = 'aeiouy';
protected $leastLettersIfFirstIsVowel = 0;
protected $leastLettersIfFirstIsConsonant = 0;
protected $language = null;
protected static $instances = array();
public $charset = 'UTF-8';
/**
* Returns the instance of the Stemmer for the given language
*
* @param string $language
* @return Stemmer
*/
public static function factory($language = null) {
if (!$language) {
$language = MMVC_Lang::$code;
}
$key = strtolower($language);
if (!isset(self::$instances[$key])) {
$className = 'Stemmer_' . ucfirst($language);
$parents = @class_parents($className);
if ($parents && in_array('Stemmer',$parents)) {
$instance = new $className;
self::$instances[$key] = $instance;
} else {
return null;
}
}
return @self::$instances[$key];
}
/**
* Returns the language of the current Stemmer instance
*
* @return string
*/
public function getLanguage() {
return $this->language;
}
/**
* returns the number of the first rule from the rule number $rule_number
* that can be applied to the given reversed form
* returns -1 if no rule can be applied, ie the stem has been found
*
* @param string $reversedForm
* @param int $ruleNumber
* @return int
*/
protected function getFirstRule($reversedForm, $ruleNumber = 0) {
$nbRules = count($this->rules);
for ($i=$ruleNumber; $i<$nbRules; ++$i) {
$rule = $this->rules[$i];
$rule = preg_replace($this->rulePattern, '\1', $rule);
// var_dump($rule, $reversedForm, mb_substr($reversedForm,0,mb_strlen($rule))); echo '<br/>';
if ($rule == mb_substr($reversedForm,0,mb_strlen($rule,$this->charset),$this->charset)) {
return $i;
}
}
return -1;
}
/**
* Check the acceptability of a stem for a given language
*
* @param string $reversed_stem the stem to check in reverse form
*/
protected function checkAcceptability($reversed_stem) {
if ( preg_match ("/[{$this->vowels}]$/", $reversed_stem) ) {
// if the form starts with a vowel then at least X letters must remain after stemming (e.g.: "étaient" --> "ét")
return mb_strlen($reversed_stem,$this->charset) > $this->leastLettersIfFirstIsVowel;
} else {
// if the form starts with a consonant then at X two letters must remain after stemming
if (mb_strlen($reversed_stem,$this->charset) < $this->leastLettersIfFirstIsConsonant) {
return False;
}
return preg_match("/[{$this->vowels}]/", $reversed_stem);
}
}
/**
* Returns the Stem indexing array for the given text. Each word's stem is stored as
* a key of the array, and the value is the number of times this stem has been met.
*
* @param string $text
* @return Array
*/
public function textStemIndex($text) {
preg_match_all('/\b[^\s\r\n\t\'\"]+\b/', mb_strtolower(mb_html_entity_decode(strip_tags($text), $this->charset), $this->charset), $matches);
$words = $matches[0];
$result = array();
foreach ($words as $word) {
if (strlen($word) > 1 && !in_array($word, $this->stopWords)) {
$stem = $this->stem($word);
if (!in_array($stem, $this->stopWords)) {
if (!isset($result[$stem])) {
$result[$stem] = 0;
}
$result[$stem] += 1;
}
}
}
return $result;
}
/**
* the actual Paice/Husk stemmer
* which returns a stem for the given form
*
* @param string $form the word for which we want the stem
*/
public function stem($form) {
static $stems = array();
if (!isset($stems[$form])) {
$intact = True ;
$stem_found = False ;
$reversed_form = mb_strrev($form, $this->charset);
$rule_number = 0 ;
// that loop goes through the rules' array until it finds an ending one (ending by '.') or the last one ('end0.')
while ( True ) {
$rule_number = $this->getFirstRule($reversed_form, $rule_number);
if ( $rule_number == - 1 ) {
// no other rule can be applied => the stem has been found
break;
}
$rule = $this->rules[$rule_number];
preg_match($this->rulePattern, $rule, $matches);
if (( $matches [ 2 ] != '*' ) || ( $intact )) {
$reversed_stem = $matches[ 4 ] . mb_substr($reversed_form , $matches[3], mb_strlen($reversed_form) - $matches[3], $this->charset);
if ( $this->checkAcceptability($reversed_stem) ) {
$reversed_form = $reversed_stem ;
if ( $matches[5] == '.' ) {
break;
}
}
else {
// go to another rule
$rule_number ++;
}
}
else {
// go to another rule
$rule_number ++;
}
}
$stems[$form] = mb_strrev($reversed_form);
}
return $stems[$form];
}
}
Le complément FR :
<?php
/**
* @author nchambrier
* @date 29 juin 07
* @encoding UTF-8
*/
class Stemmer_Fr extends Stemmer {
function __construct() {
$this->language = 'fr';
$this->charset = 'UTF-8';
$this->rulePattern = '/^([a-zàâèéêëîïôûùç]*)(\*){0,1}(\d)([a-zàâèéêëîïôûùç]*)([.|>])/';
$this->vowels = 'aàâeèéêëiîïoôuûùy';
$this->leastLettersIfFirstIsVowel = 2;
$this->leastLettersIfFirstIsConsonant = 2;
$this->rules = array(
'ug1>' , # { -gu > -g }
'emsi4.', # { -isme > - }
'etsi4.', # { -iste > - }
//'gol3.' , # { -log > - }
'isme4.' , # { -isme > - }
'iste4.' , # { -iste > - }
'euq3>' , # { -que > - }
'esre1>' , # { -erse > -ers }
'esio1>' , # { -oise > -ois }
'siol1.' , # { -lois > -loi }
'siof0.' , # { -fois > -fois }
'sioe0.' , # { -eois > -eois }
'sio3>' , # { -ois > - }
'st1>' , # { -ts > -t }
'sf1>' , # { -fs > -f }
'sle1>' , # { -els > -el }
'slo1>' , # { -ols > -ol }
'sé1>' , # { -és > -é }
'étuae5.' , # { -eauté > - }
'étuae2.' , # { -eauté > -eau }
'tnia0.' , # { -aint > -aint }
'tniv1.' , # { -vint > -vin }
'tni3>' , # { -int > - }
'suor1.' , # { -rous > -ou }
'suo0.' , # { -ous > -ous }
'sdrail5.' , # { -liards > -l }
'sdrai4.' , # { -iards > -i }
'erèi1>' , # { -ière > -ier }
'sesue3x>' , # { -euses > -euse }
'esuey5i.' , # { -yeuse > -i }
'esue2x>' , # { -euse > -eux }
'se1>' , # { -es > -e }
'erèg3.' , # { -gère > -g }
'eca1>' , # { -ace > -ac }
'esiah0.' , # { -haise > - }
'esi1>' , # { -ise > -is }
'siss2.' , # { -ssis > -ss }
'sir2>' , # { -ris > -r }
'sit2>' , # { -tis > -t }
'egané1.' , # { -énage > -énag }
'egalli6>' , # { -illage > - }
'egass1.' , # { -ssage > -sag }
'egas0.' , # { -sage > - }
'egat3.' , # { -tage > - }
'ega3>' , # { -age > - }
'ette4>' , # { -ette > - }
'ett2>' , # { -tte > -t }
'etio1.' , # { -oite > -oit }
'tioç4c.' , # { -çoit > -c }
'tio0.' , # { -oit > -oit }
'et1>' , # { -te > -t }
'eb1>' , # { -be > -b }
'snia1>' , # { -ains > -ain }
'eniatnau8>' , # { -uantaine > - }
'eniatn4.' , # { -ntaine > -nt }
'enia1>' , # { -aine > -ain }
'niatnio3.' , # { -ointain > -oint }
'niatg3.' , # { -gtain > -gt }
'eé1>' , # { -ée > -é }
'éhcat1.' , # { -taché > -tach }
'éhca4.' , # { -aché > - }
'étila5>' , # { -alité > - }
'étici5.' , # { -icité > - }
'étir1.' , # { -rité > -rit }
'éti3>' , # { -ité > - }
'égan1.' , # { -nagé > -nag }
'éga3>' , # { -agé > - }
'étehc1.' , # { -cheté > -chet }
'éte3>' , # { -eté > - }
'éit0.' , # { -tié > -tié }
'é1>' , # { -é > - }
'eire4.' , # { -erie > - }
'eirue5.' , # { -eurie > - }
'eio1.' , # { -oie > -oi }
'eia1.' , # { -aie > -ai }
'ei1>' , # { -ie > -i }
'eng1.' , # { -gne > -gn }
'xuaessi7.' , # { -isseaux > - }
'xuae1>' , # { -eaux > -eau }
'uaes0.' , # { -seau > -seau }
'uae3.' , # { -eau > - }
'xuave2l.' , # { -evaux > -eval }
'xuav2li>' , # { -vaux > -vail }
'xua3la>' , # { -aux > -al }
'ela1>' , # { -ale > -al }
'lart2.' , # { -tral > -tr }
'lani2>' , # { -inal > -in }
'laé2>' , # { -éal > -é }
'siay4i.' , # { -yais > -i }
'siassia7.' , # { -aissais > - }
'siarv1*.' , # { -vrais > -vrai if intact }
'sia1>' , # { -ais > -ai }
'tneiayo6i.' , # { -oyaient > -oi }
'tneiay6i.' , # { -yaient > -i }
'tneiassia9.' , # { -aissaient > - }
'tneiareio7.' , # { -oieraient > -oi }
'tneia5>' , # { -aient > - }
'tneia4>' , # { -aient > -a }
'tiario4.' , # { -oirait > -oi }
'tiarim3.' , # { -mirait > -mir }
'tiaria3.' , # { -airait > -air }
'tiaris3.' , # { -sirait > -sir }
'tiari5.' , # { -irait > - }
'tiarve6>' , # { -evrait > - }
'tiare5>' , # { -erait > - }
'iare4>' , # { -erai > - }
'are3>' , # { -era > - }
'tiay4i.' , # { -yait > -i }
'tia3>' , # { -ait > - }
'tnay4i.' , # { -yant > -i }
'emèiu5>' , # { -uième > - }
'emèi4>' , # { -ième > - }
'tnaun3.' , # { -nuant > -nu }
'tnauqo3.' , # { -oquant > -oqu }
'tnau4>' , # { -uant > - }
'tnaf0.' , # { -fant > -fant }
'tnaté2>' , # { -étant > -ét }
'tna3>' , # { -ant > - }
'tno3>' , # { -ont > - }
'zeiy4i.' , # { -yiez > -i }
'zey3i.' , # { -yez > -i }
'zeire5>' , # { -eriez > - }
'zeird4.' , # { -driez > -d }
'zeirio4.' , # { -oiriez > -oi }
'ze2>' , # { -ez > - }
'ssiab0.' , # { -baiss > - }
'ssia4.' , # { -aiss > - }
'ssi3.' , # { -iss > - }
'tnemma6>' , # { -amment > - }
'tnemesuey9i.' , # { -yeusement > -i }
'tnemesue8>' , # { -eusement > - }
'tnemevi7.' , # { -ivement > - }
'tnemessia5.' , # { -aissement > -aiss }
'tnemessi8.' , # { -issement > - }
'tneme5>' , # { -ement > - }
'tnemia4.' , # { -aiment > -ai }
'tnemé5>' , # { -ément > - }
'el2l>' , # { -le > -l }
'lle3le>' , # { -ell > -el }
'letô0.' , # { -ôtel > -ôtel }
'lepp0.' , # { -ppel > -ppel }
'le2>' , # { -el > - }
'srei1>' , # { -iers > -ier }
'reit3.' , # { -tier > -t }
'reila2.' , # { -alier > -ali }
'rei3>' , # { -ier > - }
'ertâe5.' , # { -eâtre > - }
'ertâé1.' , # { -éâtre > -éâtr }
'ertâ4.' , # { -âtre > - }
'drai4.' , # { -iard > - }
'erdro0.' , # { -ordre > -ordre }
'erute5.' , # { -eture > - }
'ruta0.' , # { -atur > -atur }
'eruta1.' , # { -ature > -atur }
'erutiov1.' , # { -voiture > -voitur }
'erub3.' , # { -bure > -b }
'eruh3.' , # { -hure > -h }
'erul3.' , # { -lure > -l }
'er2r>' , # { -re > -r }
'nn1>' , # { -nn > -n }
'rèi3.' , # { -ièr > - }
'srev0.' , # { -vers > -vers }
'sr1>' , # { -rs > -r }
'rid2>' , # { -dir > -d }
're2>' , # { -er > - }
'xuei4.' , # { -ieux > - }
'esuei5.' , # { -ieuse > - }
'lbati3.' , # { -itabl > -it }
'lba3>' , # { -abl > - }
'rueis0.' , # { -sieur > - }
'ruehcn4.' , # { -ncheur > -nc }
'ecirta6.' , # { -atrice > - }
'ruetai6.' , # { -iateur > - }
'rueta5.' , # { -ateur > - }
'rueir0.' , # { -rieur > - }
'rue3>' , # { -eur > - }
'esseti6.' , # { -itesse > - }
'essere6>' , # { -eresse > - }
'esserd1.' , # { -dresse > -dress }
'esse4>' , # { -esse > - }
'essiab1.' , # { -baisse > -baiss }
'essia5.' , # { -aisse > - }
'essio1.' , # { -oisse > -oiss }
'essi4.' , # { -isse > - }
'essal4.' , # { -lasse > -l }
'essa1>' , # { -asse > -ass }
'ssab1.' , # { -bass > -bas }
'essurp1.' , # { -prusse > -uss }
'essu4.' , # { -usse > - }
'essi1.' , # { -isse > -ss }
'ssor1.' , # { -ross > -ros }
'essor2.' , # { -rosse > -ros }
'esso1>' , # { -osse > -oss }
'ess2>' , # { -sse > -s }
'tio3.' , # { -oit > - }
'rès2re.' , # { -sèr > -ser }
'rè0e.' , # { -èr > -ère }
'esn1.' , # { -nse > -èns }
'eu1>' , # { -ue > -u }
'sua0.' , # { -aus > -aus }
'su1>' , # { -us > -u }
'utt1>' , # { -utt > -tt }
'tuç3c.' , # { -çut > -c }
'uç2c.' , # { -çu > -c }
'ur1.' , # { -ru > -r }
'ehcn2>' , # { -nche > -nc }
'ehcu1>' , # { -uche > -uch }
'snorr3.' , # { -rrons > -rr }
'snoru3.' , # { -urons > -ur }
'snorua3.' , # { -aurons > -aur }
'snorv3.' , # { -vrons > -vr }
'snorio4.' , # { -oirons > -oi }
'snori5.' , # { -irons > - }
'snore5>' , # { -erons > - }
'snortt4>' , # { -ttrons > -tt }
'snortîa7.' , # { -aîtrons > - }
'snort3.' , # { -trons > -tr }
'snor4.' , # { -rons > - }
'snossi6.' , # { -issons > - }
'snoire6.' , # { -erions > - }
'snoird5.' , # { -drions > -d }
'snoitai7.' , # { -iations > - }
'snoita6.' , # { -ations > - }
'snoits1>' , # { -stions > -stion }
'noits0.' , # { -stion > -stion }
'snoi4>' , # { -ions > - }
'noitaci7>' , # { -ication > - }
'noitai6.' , # { -iation > - }
'noita5.' , # { -ation > - }
'noitu4.' , # { -ution > -u }
'noi3>' , # { -ion > - }
'snoya0.' , # { -ayons > -ayons }
'snoy4i.' , # { -yons > -i }
'snoça1.' , # { -açons > -açon }
'snoçr1.' , # { -rçons > -rçon }
'snoe4.' , # { -eons > - }
'snosiar1>' , # { -raisons > - }
'snola1.' , # { -alons > -alon }
'sno3>' , # { -ons > - }
'sno1>' , # { -ons > -on }
'noll2.' , # { -llon > -ll }
'tnennei4.' , # { -iennent > -ien }
'ennei2>' , # { -ienne > -ien }
'snei1>' , # { -iens > -ien }
'sneé1>' , # { -éens > -éen }
'enneé5e.' , # { -éenne > -e }
'neé3e.' , # { -éen > -e }
'neic0.' , # { -cien > -cien }
'neiv0.' , # { -vien > -vien }
'nei3.' , # { -ien > - }
'sc1.' , # { -cs > -c }
'sd1.' , # { -ds > -d }
'sg1.' , # { -gs > -g }
'sni1.' , # { -ins > -in }
'tiu0.' , # { -uit > - }
'ti2.' , # { -it > - }
'sp1>' , # { -ps > -p }
'sna1>' , # { -ans > -an }
'sue1.' , # { -eus > -eu }
'enn2>' , # { -nne > -n }
'nong2.' , # { -gnon > -gn }
'noss2.' , # { -sson > -ss }
'rioe4.' , # { -eoir > - }
'riot0.' , # { -toir > -toir }
'riorc1.' , # { -croir > -croi }
'riovec5.' , # { -cevoir > -c }
'rio3.' , # { -oir > - }
'ric2.' , # { -cir > -l }
'ril2.' , # { -lir > -l }
'tnerim3.' , # { -mirent > -mir }
'tneris3>' , # { -sirent > -sir }
'tneri5.' , # { -irent > - }
'tîa3.' , # { -aît > - }
'riss2.' , # { -ssir > -ss }
'tî2.' , # { -ît > - }
'tâ2>' , # { -ât > - }
'ario2.' , # { -oira > -oi }
'arim1.' , # { -mira > -m }
'ara1.' , # { -ara > -ar }
'aris1.' , # { -sira > -sir }
'ari3.' , # { -ira > - }
'art1>' , # { -tra > -tr }
'ardn2.' , # { -ndra > -nd }
'arr1.' , # { -rra > -rr }
'arua1.' , # { -aura > -aur }
'aro1.' , # { -ora > -or }
'arv1.' , # { -vra > -vr }
'aru1.' , # { -ura > -ur }
'ar2.' , # { -ra > - }
'rd1.' , # { -dr > -d }
'ud1.' , # { -du > - }
'ul1.' , # { -lu > -l }
'ini1.' , # { -ini > -in }
'rin2.' , # { -nir > - }
'tnessiab3.' , # { -baissent > -baiss }
'tnessia7.' , # { -aissent > - }
'tnessi6.' , # { -issent > - }
'tnessni4.' , # { -inssent > -ins }
'sini2.' , # { -inis > -in }
'sl1.' , # { -ls > -l }
'iard3.' , # { -drai > -d }
'iario3.' , # { -oirai > -oi }
'ia2>' , # { -ai > - }
'io0.' , # { -oi > -oi }
'iule2.' , # { -elui > -el }
'i1>' , # { -i > - }
'sid2.' , # { -dis > -d }
'sic2.' , # { -cis > -c }
'esoi4.' , # { -iose > - }
'ed1.' , # { -de > -d }
'ai2>' , # { -ia > - }
'a1>' , # { -a > - }
'adr1.' , # { -rda > -rd }
'tnerè5>' , # { -èrent > - }
'evir1.' , # { -rive > -riv }
'evio4>' , # { -oive > - }
'evi3.' , # { -ive > - }
'fita4.' , # { -atif > - }
'fi2>' , # { -if > - }
'enie1.' , # { -eine > -ein }
'sare4>' , # { -eras > - }
'sari4>' , # { -iras > - }
'sard3.' , # { -dras > -d }
'sart2>' , # { -tras > -tr }
'sa2.' , # { -as > - }
'tnessa6>' , # { -assent > - }
'tnessu6>' , # { -ussent > - }
'tnegna3.' , # { -angent > -ang }
'tnegi3.' , # { -igent > -ig }
'tneg0.' , # { -gent > -gent }
'tneru5>' , # { -urent > - }
'tnemg0.' , # { -gment > -gment }
'tnerni4.' , # { -inrent > -in }
'tneiv1.' , # { -vient > -vien }
'tne3>' , # { -ent > - }
'une1.' , # { -enu > -en }
'en1>' , # { -ne > -n }
'nitn2.' , # { -ntin > - }
'ecnay5i.' , # { -yance > -i }
'ecnal1.' , # { -lance > -lanc }
'ecna4.' , # { -ance > - }
'ec1>' , # { -ce > -c }
'nn1.' , # { -nn > -n }
'rit2>' , # { -tir > - }
'rut2>' , # { -tur > -t }
'rud2.' , # { -dur > -d }
'ugn1>' , # { -ngu > -ng }
'eg1>' , # { -ge > -g }
'tuo0.' , # { -out > -out }
'tul2>' , # { -lut > -l }
'tû2>' , # { -ût > - }
'ev1>' , # { -ve > -v }
'vè2ve>' , # { -èv > -ev }
'rtt1>' , # { -ttr > -tt }
'emissi6.' , # { -issime > - }
'em1.' , # { -me > -m }
'ehc1.' , # { -che > -ch }
'céi2cè.' , # { -iéc > -ièc }
'libi2l.' , # { -ibil > -ibl }
'llie1.' , # { -eill > -eil }
'liei4i.' , # { -ieil > -i }
'xuev1.' , # { -veux > -veu }
'xuey4i.' , # { -yeux > -i }
'xueni5>' , # { -ineux > - }
'xuell4.' , # { -lleux > -l }
'xuere5.' , # { -ereux > - }
'xue3>' , # { -eux > - }
'rbé3rbè.' , # { -ébr > -èbr }
'tur2.' , # { -rut > -r }
'riré4re.' , # { -érir > -er }
'rir2.' , # { -rir > -r }
'câ2ca.' , # { -âc > -ac }
'snu1.' , # { -uns > -un }
'rtîa4.' , # { -aîtr > - }
'long2.' , # { -gnol > -gn }
'vec2.' , # { -cev > -c }
'ç1c>' , # { -ç > -c }
'ssilp3.' , # { -pliss > -pl }
'silp2.' , # { -plis > -pl }
'tèhc2te.' , # { -chèt > -chet }
'nèm2ne.' , # { -mèn > -men }
'llepp1.' , # { -ppell > -ppel }
'tan2.' , # { -nat > -n }
'rvè3rve.' , # { -èvr > -evr }
'rvé3rve.' , # { -évr > -evr }
'rè2re.' , # { -èr > -er }
'ré2re.' , # { -ér > -er }
'tè2te.' , # { -èt > -et }
'té2te.' , # { -ét > -et }
'epp1.' , # { -ppe > -pp }
'eya2i.' , # { -aye > -ai }
'ya1i.' , # { -ay > -ai }
'yo1i.' , # { -oy > -oi }
'esu1.' , # { -use > -us }
'ugi1.' , # { -igu > -g }
'tt1.' , # { -tt > -t }
'e1.',
# end rule: the stem has already been found
'end0.'
);
$this->stopWords = array(
'alors',
'au',
'aucuns',
'aussi',
'autre',
'avant',
'avec',
'avoir',
'bon',
'car',
'ce',
'cela',
'ces',
'ceux',
'chaque',
'ci',
'comme',
'comment',
'dans',
'de',
'des',
'du',
'dedans',
'dehors',
'depuis',
'deux',
'devrait',
'doit',
'donc',
'dos',
'droite',
'début',
'elle',
'elles',
'en',
'encore',
'essai',
'est',
'et',
'eu',
'fait',
'faites',
'fois',
'font',
'force',
'haut',
'hors',
'ici',
'il',
'ils',
'je',
'juste',
'la',
'le',
'les',
'leur',
'là',
'ma',
'maintenant',
'mais',
'mes',
'mine',
'moins',
'mon',
'mot',
'même',
'ni',
'nommés',
'notre',
'nous',
'nouveaux',
'ou',
'où',
'par',
'parce',
'parole',
'pas',
'personnes',
'peut',
'peu',
'pièce',
'plupart',
'pour',
'pourquoi',
'quand',
'que',
'quel',
'quelle',
'quelles',
'quels',
'qui',
'sa',
'sans',
'se',
'ses',
'seulement',
'si',
'sien',
'son',
'sont',
'sous',
'soyez',
'sujet',
'sur',
'ta',
'tandis',
'tellement',
'tels',
'tes',
'ton',
'tous',
'tout',
'trop',
'très',
'tu',
'valeur',
'voie',
'voient',
'vont',
'votre',
'vous',
'vu',
'ça',
'étaient',
'état',
'étions',
'été',
'être',
);
}
}
Le complément EN :
<?php
/**
* @author nchambrier
* @date 29 juin 07
* @encoding UTF-8
*/
class Stemmer_En extends Stemmer {
function __construct() {
$this->language = 'en';
$this->charset = 'UTF-8';
$this->rulePattern = '/^([a-z]*)(\*){0,1}(\d)([a-z]*)([.|>])/';
$this->vowels = 'aeiouy';
$this->leastLettersIfFirstIsVowel = 2;
$this->leastLettersIfFirstIsConsonant = 3;
$this->rules = array(
'ai*2.', # { -ia > - if intact }
'a*1.', # { -a > - if intact }
'bb1.', # { -bb > -b }
'city3s.', # { -ytic > -ys }
'ci2>', # { -ic > - }
'cn1t>', # { -nc > -nt }
'dd1.', # { -dd > -d }
'dei3y>', # { -ied > -y }
'deec2ss.', # { -ceed > -cess }
'dee1.', # { -eed > -ee }
'de2>', # { -ed > - }
'dooh4>', # { -hood > - }
'e1>', # { -e > - }
'feil1v.', # { -lief > -liev }
'fi2>', # { -if > - }
'gni3>', # { -ing > - }
'gai3y.', # { -iag > -y }
'ga2>', # { -ag > - }
'gg1.', # { -gg > -g }
'ht*2.', # { -th > - if intact }
'hsiug5ct.', # { -guish > -ct }
'hsi3>', # { -ish > - }
'i*1.', # { -i > - if intact }
'i1y>', # { -i > -y }
'@i1d.', # { -i@ > -id -- see nois4@> & vis3@> }
'juf1s.', # { -fuj > -fus }
'ju1d.', # { -uj > -ud }
'jo1d.', # { -oj > -od }
'jeh1r.', # { -hej > -her }
'jrev1t.', # { -verj > -vert }
'jsim2t.', # { -misj > -mit }
'jn1d.', # { -nj > -nd }
'j1s.', # { -j > -s }
'lbaifi6.', # { -ifiabl > - }
'lbai4y.', # { -iabl > -y }
'lba3>', # { -abl > - }
'lbi3.', # { -ibl > - }
'lib2l>', # { -bil > -bl }
'lc1.', # { -cl > c }
'lufi4y.', # { -iful > -y }
'luf3>', # { -ful > - }
'lu2.', # { -ul > - }
'lai3>', # { -ial > - }
'lau3>', # { -ual > - }
'la2>', # { -al > - }
'll1.', # { -ll > -l }
'mui3.', # { -ium > - }
'mu*2.', # { -um > - if intact }
'msi3>', # { -ism > - }
'mm1.', # { -mm > -m }
'nois4@>', # { -sion > -@ }
'noix4ct.', # { -xion > -ct }
'noi3>', # { -ion > - }
'nai3>', # { -ian > - }
'na2>', # { -an > - }
'nee0.', # { protect -een }
'ne2>', # { -en > - }
'nn1.', # { -nn > -n }
'pihs4>', # { -ship > - }
'pp1.', # { -pp > -p }
're2>', # { -er > - }
'rae0.', # { protect -ear }
'ra2.', # { -ar > - }
'ro2>', # { -or > - }
'ru2>', # { -ur > - }
'rr1.', # { -rr > -r }
'rt1>', # { -tr > -t }
'rei3y>', # { -ier > -y }
'sei3y>', # { -ies > -y }
'sis2.', # { -sis > -s }
'si2>', # { -is > - }
'ssen4>', # { -ness > - }
'ss0.', # { protect -ss }
'suo3>', # { -ous > - }
'su*2.', # { -us > - if intact }
's*1>', # { -s > - if intact }
's0.', # { -s > -s }
'tacilp4y.', # { -plicat > -ply }
'ta2>', # { -at > - }
'tnem4>', # { -ment > - }
'tne3>', # { -ent > - }
'tna3>', # { -ant > - }
'tpir2b.', # { -ript > -rib }
'tpro2b.', # { -orpt > -orb }
'tcud1.', # { -duct > -duc }
'tpmus2.', # { -sumpt > -sum }
'tpec2iv.', # { -cept > -ceiv }
'tulo2v.', # { -olut > -olv }
'tsis0.', # { protect -sist }
'tsi3>', # { -ist > - }
'tt1.', # { -tt > -t }
'uqi3.', # { -iqu > - }
'ugo1.', # { -ogu > -og }
'vis3@>', # { -siv > -@ }
'vie0.', # { protect -eiv }
'vi2>', # { -iv > - }
'ylb1>', # { -bly > -bl }
'yli3y>', # { -ily > -y }
'ylp0.', # { protect -ply }
'yl2>', # { -ly > - }
'ygo1.', # { -ogy > -og }
'yhp1.', # { -phy > -ph }
'ymo1.', # { -omy > -om }
'ypo1.', # { -opy > -op }
'yti3>', # { -ity > - }
'yte3>', # { -ety > - }
'ytl2.', # { -lty > -l }
'yrtsi5.', # { -istry > - }
'yra3>', # { -ary > - }
'yro3>', # { -ory > - }
'yfi3.', # { -ify > - }
'ycn2t>', # { -ncy > -nt }
'yca3>', # { -acy > - }
'zi2>', # { -iz > - }
'zy1s.', # { -yz > -ys }
'end0.' # end rule: the stem has already been found
);
$this->stopWords = array(
'a',
'about',
'an',
'are',
'as',
'at',
'be',
'by',
'com',
'de',
'en',
'for',
'from',
'how',
'i',
'in',
'is',
'it',
'la',
'my',
'of',
'on',
'or',
'that',
'the',
'this',
'to',
'very',
'was',
'what',
'when',
'where',
'who',
'will',
'with',
'und',
'the',
'www',
);
}
}
Je vous laisse faire mumuse avec ça

en sachant que c'est forcément toujours incomplet, je pense qu'il manquera toujours un stop-word ou une règle. Le but dans ces système c'est de choisir à partir de quel moment on s'arrête.
Voilà le stemmer dont je parlais, il s'agit du stemmer "Paice/Husk". L'auteur original du script est en commentaires, je me suis contenté de faire un refactoring pour le passer en POO, et de compléter un peu la liste des règles FR (c'est là qu'est le gros du travail, qui n'est je pense jamais totalement terminé).
[b]Note importante[/b] : j'ai écrit cette classe pour un site que j'ai repris et qui avait des problèmes assez bizarres d'encodage que je n'avais pas le temps de régler. Il y a donc quelques bidouilles de charset qui mériteraient d'être éclaircies pour une utilisation globale.
Pour l'utiliser c'est assez simple :
- Inclure "Stemmer_FR" ou "Stemmer_EN" (selon la langue à utiliser) ou s'assurer qu'ils seront autochargés
- Include "Stemmer" ou s'assurer qu'il sera autochargé
- Instancier l'objet avec $stemmer = Stemmer::factory('fr') ou Stemmer::factory('en')
- Appeler $stemmer->textStemIndex($monTexte) pour un texte
- On peut aussi appeler simplement $stemmer->stem($monMot) pour un simple mot
La class principale (note: dans la version que j'utilise réellement il y a également tout un set de fonctions destinées à ezPdo, afin de rendre automatique et persistante l'indexation de tout objet héritant de Stemmer, je vous en fais grâce) :
[php]<?php
/**
* Paice/Husk Stemmer
* This code is in the public domain.
* @author original author : Alexis Ulrich (http://alx2002.free.fr)
* @author oop refactoring : Nicolas Chambrier (
[email protected])
*/
if (!function_exists('mb_strrev')) {
/**
* mb-string version of strrev
*
* @param string $string
* @param string $charset
* @return string
*/
function mb_strrev($string, $charset = null) {
if ($charset === null) {
$charset = mb_detect_encoding($string);
}
if ($charset == 'UTF-8') {
$string = utf8_decode($string);
$string = strrev($string);
$string = utf8_encode($string);
} else {
$string = strrev($string);
}
return $string;
}
}
if (!function_exists('mb_html_entity_decode')) {
/**
* mb-string version of html_entity_decode
*
* @param string $string
* @param string $charset
* @return string
*/
function mb_html_entity_decode($string, $charset = null) {
if ($charset === null) {
$charset = mb_detect_encoding($string);
}
if ($charset == 'UTF-8') {
$string = html_entity_decode($string);
$string = utf8_encode($string);
} else {
$string = html_entity_decode($string);
}
return $string;
}
}
/**
* Main class
*
* @see Stemmer::factory()
*/
class Stemmer {
protected $rulePattern = '/^([a-z]*)(\*){0,1}(\d)([a-z]*)([.|>])/';
protected $rules = array('end0.');
protected $stopWords = array();
protected $vowels = 'aeiouy';
protected $leastLettersIfFirstIsVowel = 0;
protected $leastLettersIfFirstIsConsonant = 0;
protected $language = null;
protected static $instances = array();
public $charset = 'UTF-8';
/**
* Returns the instance of the Stemmer for the given language
*
* @param string $language
* @return Stemmer
*/
public static function factory($language = null) {
if (!$language) {
$language = MMVC_Lang::$code;
}
$key = strtolower($language);
if (!isset(self::$instances[$key])) {
$className = 'Stemmer_' . ucfirst($language);
$parents = @class_parents($className);
if ($parents && in_array('Stemmer',$parents)) {
$instance = new $className;
self::$instances[$key] = $instance;
} else {
return null;
}
}
return @self::$instances[$key];
}
/**
* Returns the language of the current Stemmer instance
*
* @return string
*/
public function getLanguage() {
return $this->language;
}
/**
* returns the number of the first rule from the rule number $rule_number
* that can be applied to the given reversed form
* returns -1 if no rule can be applied, ie the stem has been found
*
* @param string $reversedForm
* @param int $ruleNumber
* @return int
*/
protected function getFirstRule($reversedForm, $ruleNumber = 0) {
$nbRules = count($this->rules);
for ($i=$ruleNumber; $i<$nbRules; ++$i) {
$rule = $this->rules[$i];
$rule = preg_replace($this->rulePattern, '\1', $rule);
// var_dump($rule, $reversedForm, mb_substr($reversedForm,0,mb_strlen($rule))); echo '<br/>';
if ($rule == mb_substr($reversedForm,0,mb_strlen($rule,$this->charset),$this->charset)) {
return $i;
}
}
return -1;
}
/**
* Check the acceptability of a stem for a given language
*
* @param string $reversed_stem the stem to check in reverse form
*/
protected function checkAcceptability($reversed_stem) {
if ( preg_match ("/[{$this->vowels}]$/", $reversed_stem) ) {
// if the form starts with a vowel then at least X letters must remain after stemming (e.g.: "étaient" --> "ét")
return mb_strlen($reversed_stem,$this->charset) > $this->leastLettersIfFirstIsVowel;
} else {
// if the form starts with a consonant then at X two letters must remain after stemming
if (mb_strlen($reversed_stem,$this->charset) < $this->leastLettersIfFirstIsConsonant) {
return False;
}
return preg_match("/[{$this->vowels}]/", $reversed_stem);
}
}
/**
* Returns the Stem indexing array for the given text. Each word's stem is stored as
* a key of the array, and the value is the number of times this stem has been met.
*
* @param string $text
* @return Array
*/
public function textStemIndex($text) {
preg_match_all('/\b[^\s\r\n\t\'\"]+\b/', mb_strtolower(mb_html_entity_decode(strip_tags($text), $this->charset), $this->charset), $matches);
$words = $matches[0];
$result = array();
foreach ($words as $word) {
if (strlen($word) > 1 && !in_array($word, $this->stopWords)) {
$stem = $this->stem($word);
if (!in_array($stem, $this->stopWords)) {
if (!isset($result[$stem])) {
$result[$stem] = 0;
}
$result[$stem] += 1;
}
}
}
return $result;
}
/**
* the actual Paice/Husk stemmer
* which returns a stem for the given form
*
* @param string $form the word for which we want the stem
*/
public function stem($form) {
static $stems = array();
if (!isset($stems[$form])) {
$intact = True ;
$stem_found = False ;
$reversed_form = mb_strrev($form, $this->charset);
$rule_number = 0 ;
// that loop goes through the rules' array until it finds an ending one (ending by '.') or the last one ('end0.')
while ( True ) {
$rule_number = $this->getFirstRule($reversed_form, $rule_number);
if ( $rule_number == - 1 ) {
// no other rule can be applied => the stem has been found
break;
}
$rule = $this->rules[$rule_number];
preg_match($this->rulePattern, $rule, $matches);
if (( $matches [ 2 ] != '*' ) || ( $intact )) {
$reversed_stem = $matches[ 4 ] . mb_substr($reversed_form , $matches[3], mb_strlen($reversed_form) - $matches[3], $this->charset);
if ( $this->checkAcceptability($reversed_stem) ) {
$reversed_form = $reversed_stem ;
if ( $matches[5] == '.' ) {
break;
}
}
else {
// go to another rule
$rule_number ++;
}
}
else {
// go to another rule
$rule_number ++;
}
}
$stems[$form] = mb_strrev($reversed_form);
}
return $stems[$form];
}
}[/php]
Le complément FR :
[php]<?php
/**
* @author nchambrier
* @date 29 juin 07
* @encoding UTF-8
*/
class Stemmer_Fr extends Stemmer {
function __construct() {
$this->language = 'fr';
$this->charset = 'UTF-8';
$this->rulePattern = '/^([a-zàâèéêëîïôûùç]*)(\*){0,1}(\d)([a-zàâèéêëîïôûùç]*)([.|>])/';
$this->vowels = 'aàâeèéêëiîïoôuûùy';
$this->leastLettersIfFirstIsVowel = 2;
$this->leastLettersIfFirstIsConsonant = 2;
$this->rules = array(
'ug1>' , # { -gu > -g }
'emsi4.', # { -isme > - }
'etsi4.', # { -iste > - }
//'gol3.' , # { -log > - }
'isme4.' , # { -isme > - }
'iste4.' , # { -iste > - }
'euq3>' , # { -que > - }
'esre1>' , # { -erse > -ers }
'esio1>' , # { -oise > -ois }
'siol1.' , # { -lois > -loi }
'siof0.' , # { -fois > -fois }
'sioe0.' , # { -eois > -eois }
'sio3>' , # { -ois > - }
'st1>' , # { -ts > -t }
'sf1>' , # { -fs > -f }
'sle1>' , # { -els > -el }
'slo1>' , # { -ols > -ol }
'sé1>' , # { -és > -é }
'étuae5.' , # { -eauté > - }
'étuae2.' , # { -eauté > -eau }
'tnia0.' , # { -aint > -aint }
'tniv1.' , # { -vint > -vin }
'tni3>' , # { -int > - }
'suor1.' , # { -rous > -ou }
'suo0.' , # { -ous > -ous }
'sdrail5.' , # { -liards > -l }
'sdrai4.' , # { -iards > -i }
'erèi1>' , # { -ière > -ier }
'sesue3x>' , # { -euses > -euse }
'esuey5i.' , # { -yeuse > -i }
'esue2x>' , # { -euse > -eux }
'se1>' , # { -es > -e }
'erèg3.' , # { -gère > -g }
'eca1>' , # { -ace > -ac }
'esiah0.' , # { -haise > - }
'esi1>' , # { -ise > -is }
'siss2.' , # { -ssis > -ss }
'sir2>' , # { -ris > -r }
'sit2>' , # { -tis > -t }
'egané1.' , # { -énage > -énag }
'egalli6>' , # { -illage > - }
'egass1.' , # { -ssage > -sag }
'egas0.' , # { -sage > - }
'egat3.' , # { -tage > - }
'ega3>' , # { -age > - }
'ette4>' , # { -ette > - }
'ett2>' , # { -tte > -t }
'etio1.' , # { -oite > -oit }
'tioç4c.' , # { -çoit > -c }
'tio0.' , # { -oit > -oit }
'et1>' , # { -te > -t }
'eb1>' , # { -be > -b }
'snia1>' , # { -ains > -ain }
'eniatnau8>' , # { -uantaine > - }
'eniatn4.' , # { -ntaine > -nt }
'enia1>' , # { -aine > -ain }
'niatnio3.' , # { -ointain > -oint }
'niatg3.' , # { -gtain > -gt }
'eé1>' , # { -ée > -é }
'éhcat1.' , # { -taché > -tach }
'éhca4.' , # { -aché > - }
'étila5>' , # { -alité > - }
'étici5.' , # { -icité > - }
'étir1.' , # { -rité > -rit }
'éti3>' , # { -ité > - }
'égan1.' , # { -nagé > -nag }
'éga3>' , # { -agé > - }
'étehc1.' , # { -cheté > -chet }
'éte3>' , # { -eté > - }
'éit0.' , # { -tié > -tié }
'é1>' , # { -é > - }
'eire4.' , # { -erie > - }
'eirue5.' , # { -eurie > - }
'eio1.' , # { -oie > -oi }
'eia1.' , # { -aie > -ai }
'ei1>' , # { -ie > -i }
'eng1.' , # { -gne > -gn }
'xuaessi7.' , # { -isseaux > - }
'xuae1>' , # { -eaux > -eau }
'uaes0.' , # { -seau > -seau }
'uae3.' , # { -eau > - }
'xuave2l.' , # { -evaux > -eval }
'xuav2li>' , # { -vaux > -vail }
'xua3la>' , # { -aux > -al }
'ela1>' , # { -ale > -al }
'lart2.' , # { -tral > -tr }
'lani2>' , # { -inal > -in }
'laé2>' , # { -éal > -é }
'siay4i.' , # { -yais > -i }
'siassia7.' , # { -aissais > - }
'siarv1*.' , # { -vrais > -vrai if intact }
'sia1>' , # { -ais > -ai }
'tneiayo6i.' , # { -oyaient > -oi }
'tneiay6i.' , # { -yaient > -i }
'tneiassia9.' , # { -aissaient > - }
'tneiareio7.' , # { -oieraient > -oi }
'tneia5>' , # { -aient > - }
'tneia4>' , # { -aient > -a }
'tiario4.' , # { -oirait > -oi }
'tiarim3.' , # { -mirait > -mir }
'tiaria3.' , # { -airait > -air }
'tiaris3.' , # { -sirait > -sir }
'tiari5.' , # { -irait > - }
'tiarve6>' , # { -evrait > - }
'tiare5>' , # { -erait > - }
'iare4>' , # { -erai > - }
'are3>' , # { -era > - }
'tiay4i.' , # { -yait > -i }
'tia3>' , # { -ait > - }
'tnay4i.' , # { -yant > -i }
'emèiu5>' , # { -uième > - }
'emèi4>' , # { -ième > - }
'tnaun3.' , # { -nuant > -nu }
'tnauqo3.' , # { -oquant > -oqu }
'tnau4>' , # { -uant > - }
'tnaf0.' , # { -fant > -fant }
'tnaté2>' , # { -étant > -ét }
'tna3>' , # { -ant > - }
'tno3>' , # { -ont > - }
'zeiy4i.' , # { -yiez > -i }
'zey3i.' , # { -yez > -i }
'zeire5>' , # { -eriez > - }
'zeird4.' , # { -driez > -d }
'zeirio4.' , # { -oiriez > -oi }
'ze2>' , # { -ez > - }
'ssiab0.' , # { -baiss > - }
'ssia4.' , # { -aiss > - }
'ssi3.' , # { -iss > - }
'tnemma6>' , # { -amment > - }
'tnemesuey9i.' , # { -yeusement > -i }
'tnemesue8>' , # { -eusement > - }
'tnemevi7.' , # { -ivement > - }
'tnemessia5.' , # { -aissement > -aiss }
'tnemessi8.' , # { -issement > - }
'tneme5>' , # { -ement > - }
'tnemia4.' , # { -aiment > -ai }
'tnemé5>' , # { -ément > - }
'el2l>' , # { -le > -l }
'lle3le>' , # { -ell > -el }
'letô0.' , # { -ôtel > -ôtel }
'lepp0.' , # { -ppel > -ppel }
'le2>' , # { -el > - }
'srei1>' , # { -iers > -ier }
'reit3.' , # { -tier > -t }
'reila2.' , # { -alier > -ali }
'rei3>' , # { -ier > - }
'ertâe5.' , # { -eâtre > - }
'ertâé1.' , # { -éâtre > -éâtr }
'ertâ4.' , # { -âtre > - }
'drai4.' , # { -iard > - }
'erdro0.' , # { -ordre > -ordre }
'erute5.' , # { -eture > - }
'ruta0.' , # { -atur > -atur }
'eruta1.' , # { -ature > -atur }
'erutiov1.' , # { -voiture > -voitur }
'erub3.' , # { -bure > -b }
'eruh3.' , # { -hure > -h }
'erul3.' , # { -lure > -l }
'er2r>' , # { -re > -r }
'nn1>' , # { -nn > -n }
'rèi3.' , # { -ièr > - }
'srev0.' , # { -vers > -vers }
'sr1>' , # { -rs > -r }
'rid2>' , # { -dir > -d }
're2>' , # { -er > - }
'xuei4.' , # { -ieux > - }
'esuei5.' , # { -ieuse > - }
'lbati3.' , # { -itabl > -it }
'lba3>' , # { -abl > - }
'rueis0.' , # { -sieur > - }
'ruehcn4.' , # { -ncheur > -nc }
'ecirta6.' , # { -atrice > - }
'ruetai6.' , # { -iateur > - }
'rueta5.' , # { -ateur > - }
'rueir0.' , # { -rieur > - }
'rue3>' , # { -eur > - }
'esseti6.' , # { -itesse > - }
'essere6>' , # { -eresse > - }
'esserd1.' , # { -dresse > -dress }
'esse4>' , # { -esse > - }
'essiab1.' , # { -baisse > -baiss }
'essia5.' , # { -aisse > - }
'essio1.' , # { -oisse > -oiss }
'essi4.' , # { -isse > - }
'essal4.' , # { -lasse > -l }
'essa1>' , # { -asse > -ass }
'ssab1.' , # { -bass > -bas }
'essurp1.' , # { -prusse > -uss }
'essu4.' , # { -usse > - }
'essi1.' , # { -isse > -ss }
'ssor1.' , # { -ross > -ros }
'essor2.' , # { -rosse > -ros }
'esso1>' , # { -osse > -oss }
'ess2>' , # { -sse > -s }
'tio3.' , # { -oit > - }
'rès2re.' , # { -sèr > -ser }
'rè0e.' , # { -èr > -ère }
'esn1.' , # { -nse > -èns }
'eu1>' , # { -ue > -u }
'sua0.' , # { -aus > -aus }
'su1>' , # { -us > -u }
'utt1>' , # { -utt > -tt }
'tuç3c.' , # { -çut > -c }
'uç2c.' , # { -çu > -c }
'ur1.' , # { -ru > -r }
'ehcn2>' , # { -nche > -nc }
'ehcu1>' , # { -uche > -uch }
'snorr3.' , # { -rrons > -rr }
'snoru3.' , # { -urons > -ur }
'snorua3.' , # { -aurons > -aur }
'snorv3.' , # { -vrons > -vr }
'snorio4.' , # { -oirons > -oi }
'snori5.' , # { -irons > - }
'snore5>' , # { -erons > - }
'snortt4>' , # { -ttrons > -tt }
'snortîa7.' , # { -aîtrons > - }
'snort3.' , # { -trons > -tr }
'snor4.' , # { -rons > - }
'snossi6.' , # { -issons > - }
'snoire6.' , # { -erions > - }
'snoird5.' , # { -drions > -d }
'snoitai7.' , # { -iations > - }
'snoita6.' , # { -ations > - }
'snoits1>' , # { -stions > -stion }
'noits0.' , # { -stion > -stion }
'snoi4>' , # { -ions > - }
'noitaci7>' , # { -ication > - }
'noitai6.' , # { -iation > - }
'noita5.' , # { -ation > - }
'noitu4.' , # { -ution > -u }
'noi3>' , # { -ion > - }
'snoya0.' , # { -ayons > -ayons }
'snoy4i.' , # { -yons > -i }
'snoça1.' , # { -açons > -açon }
'snoçr1.' , # { -rçons > -rçon }
'snoe4.' , # { -eons > - }
'snosiar1>' , # { -raisons > - }
'snola1.' , # { -alons > -alon }
'sno3>' , # { -ons > - }
'sno1>' , # { -ons > -on }
'noll2.' , # { -llon > -ll }
'tnennei4.' , # { -iennent > -ien }
'ennei2>' , # { -ienne > -ien }
'snei1>' , # { -iens > -ien }
'sneé1>' , # { -éens > -éen }
'enneé5e.' , # { -éenne > -e }
'neé3e.' , # { -éen > -e }
'neic0.' , # { -cien > -cien }
'neiv0.' , # { -vien > -vien }
'nei3.' , # { -ien > - }
'sc1.' , # { -cs > -c }
'sd1.' , # { -ds > -d }
'sg1.' , # { -gs > -g }
'sni1.' , # { -ins > -in }
'tiu0.' , # { -uit > - }
'ti2.' , # { -it > - }
'sp1>' , # { -ps > -p }
'sna1>' , # { -ans > -an }
'sue1.' , # { -eus > -eu }
'enn2>' , # { -nne > -n }
'nong2.' , # { -gnon > -gn }
'noss2.' , # { -sson > -ss }
'rioe4.' , # { -eoir > - }
'riot0.' , # { -toir > -toir }
'riorc1.' , # { -croir > -croi }
'riovec5.' , # { -cevoir > -c }
'rio3.' , # { -oir > - }
'ric2.' , # { -cir > -l }
'ril2.' , # { -lir > -l }
'tnerim3.' , # { -mirent > -mir }
'tneris3>' , # { -sirent > -sir }
'tneri5.' , # { -irent > - }
'tîa3.' , # { -aît > - }
'riss2.' , # { -ssir > -ss }
'tî2.' , # { -ît > - }
'tâ2>' , # { -ât > - }
'ario2.' , # { -oira > -oi }
'arim1.' , # { -mira > -m }
'ara1.' , # { -ara > -ar }
'aris1.' , # { -sira > -sir }
'ari3.' , # { -ira > - }
'art1>' , # { -tra > -tr }
'ardn2.' , # { -ndra > -nd }
'arr1.' , # { -rra > -rr }
'arua1.' , # { -aura > -aur }
'aro1.' , # { -ora > -or }
'arv1.' , # { -vra > -vr }
'aru1.' , # { -ura > -ur }
'ar2.' , # { -ra > - }
'rd1.' , # { -dr > -d }
'ud1.' , # { -du > - }
'ul1.' , # { -lu > -l }
'ini1.' , # { -ini > -in }
'rin2.' , # { -nir > - }
'tnessiab3.' , # { -baissent > -baiss }
'tnessia7.' , # { -aissent > - }
'tnessi6.' , # { -issent > - }
'tnessni4.' , # { -inssent > -ins }
'sini2.' , # { -inis > -in }
'sl1.' , # { -ls > -l }
'iard3.' , # { -drai > -d }
'iario3.' , # { -oirai > -oi }
'ia2>' , # { -ai > - }
'io0.' , # { -oi > -oi }
'iule2.' , # { -elui > -el }
'i1>' , # { -i > - }
'sid2.' , # { -dis > -d }
'sic2.' , # { -cis > -c }
'esoi4.' , # { -iose > - }
'ed1.' , # { -de > -d }
'ai2>' , # { -ia > - }
'a1>' , # { -a > - }
'adr1.' , # { -rda > -rd }
'tnerè5>' , # { -èrent > - }
'evir1.' , # { -rive > -riv }
'evio4>' , # { -oive > - }
'evi3.' , # { -ive > - }
'fita4.' , # { -atif > - }
'fi2>' , # { -if > - }
'enie1.' , # { -eine > -ein }
'sare4>' , # { -eras > - }
'sari4>' , # { -iras > - }
'sard3.' , # { -dras > -d }
'sart2>' , # { -tras > -tr }
'sa2.' , # { -as > - }
'tnessa6>' , # { -assent > - }
'tnessu6>' , # { -ussent > - }
'tnegna3.' , # { -angent > -ang }
'tnegi3.' , # { -igent > -ig }
'tneg0.' , # { -gent > -gent }
'tneru5>' , # { -urent > - }
'tnemg0.' , # { -gment > -gment }
'tnerni4.' , # { -inrent > -in }
'tneiv1.' , # { -vient > -vien }
'tne3>' , # { -ent > - }
'une1.' , # { -enu > -en }
'en1>' , # { -ne > -n }
'nitn2.' , # { -ntin > - }
'ecnay5i.' , # { -yance > -i }
'ecnal1.' , # { -lance > -lanc }
'ecna4.' , # { -ance > - }
'ec1>' , # { -ce > -c }
'nn1.' , # { -nn > -n }
'rit2>' , # { -tir > - }
'rut2>' , # { -tur > -t }
'rud2.' , # { -dur > -d }
'ugn1>' , # { -ngu > -ng }
'eg1>' , # { -ge > -g }
'tuo0.' , # { -out > -out }
'tul2>' , # { -lut > -l }
'tû2>' , # { -ût > - }
'ev1>' , # { -ve > -v }
'vè2ve>' , # { -èv > -ev }
'rtt1>' , # { -ttr > -tt }
'emissi6.' , # { -issime > - }
'em1.' , # { -me > -m }
'ehc1.' , # { -che > -ch }
'céi2cè.' , # { -iéc > -ièc }
'libi2l.' , # { -ibil > -ibl }
'llie1.' , # { -eill > -eil }
'liei4i.' , # { -ieil > -i }
'xuev1.' , # { -veux > -veu }
'xuey4i.' , # { -yeux > -i }
'xueni5>' , # { -ineux > - }
'xuell4.' , # { -lleux > -l }
'xuere5.' , # { -ereux > - }
'xue3>' , # { -eux > - }
'rbé3rbè.' , # { -ébr > -èbr }
'tur2.' , # { -rut > -r }
'riré4re.' , # { -érir > -er }
'rir2.' , # { -rir > -r }
'câ2ca.' , # { -âc > -ac }
'snu1.' , # { -uns > -un }
'rtîa4.' , # { -aîtr > - }
'long2.' , # { -gnol > -gn }
'vec2.' , # { -cev > -c }
'ç1c>' , # { -ç > -c }
'ssilp3.' , # { -pliss > -pl }
'silp2.' , # { -plis > -pl }
'tèhc2te.' , # { -chèt > -chet }
'nèm2ne.' , # { -mèn > -men }
'llepp1.' , # { -ppell > -ppel }
'tan2.' , # { -nat > -n }
'rvè3rve.' , # { -èvr > -evr }
'rvé3rve.' , # { -évr > -evr }
'rè2re.' , # { -èr > -er }
'ré2re.' , # { -ér > -er }
'tè2te.' , # { -èt > -et }
'té2te.' , # { -ét > -et }
'epp1.' , # { -ppe > -pp }
'eya2i.' , # { -aye > -ai }
'ya1i.' , # { -ay > -ai }
'yo1i.' , # { -oy > -oi }
'esu1.' , # { -use > -us }
'ugi1.' , # { -igu > -g }
'tt1.' , # { -tt > -t }
'e1.',
# end rule: the stem has already been found
'end0.'
);
$this->stopWords = array(
'alors',
'au',
'aucuns',
'aussi',
'autre',
'avant',
'avec',
'avoir',
'bon',
'car',
'ce',
'cela',
'ces',
'ceux',
'chaque',
'ci',
'comme',
'comment',
'dans',
'de',
'des',
'du',
'dedans',
'dehors',
'depuis',
'deux',
'devrait',
'doit',
'donc',
'dos',
'droite',
'début',
'elle',
'elles',
'en',
'encore',
'essai',
'est',
'et',
'eu',
'fait',
'faites',
'fois',
'font',
'force',
'haut',
'hors',
'ici',
'il',
'ils',
'je',
'juste',
'la',
'le',
'les',
'leur',
'là',
'ma',
'maintenant',
'mais',
'mes',
'mine',
'moins',
'mon',
'mot',
'même',
'ni',
'nommés',
'notre',
'nous',
'nouveaux',
'ou',
'où',
'par',
'parce',
'parole',
'pas',
'personnes',
'peut',
'peu',
'pièce',
'plupart',
'pour',
'pourquoi',
'quand',
'que',
'quel',
'quelle',
'quelles',
'quels',
'qui',
'sa',
'sans',
'se',
'ses',
'seulement',
'si',
'sien',
'son',
'sont',
'sous',
'soyez',
'sujet',
'sur',
'ta',
'tandis',
'tellement',
'tels',
'tes',
'ton',
'tous',
'tout',
'trop',
'très',
'tu',
'valeur',
'voie',
'voient',
'vont',
'votre',
'vous',
'vu',
'ça',
'étaient',
'état',
'étions',
'été',
'être',
);
}
}[/php]
Le complément EN :
[php]<?php
/**
* @author nchambrier
* @date 29 juin 07
* @encoding UTF-8
*/
class Stemmer_En extends Stemmer {
function __construct() {
$this->language = 'en';
$this->charset = 'UTF-8';
$this->rulePattern = '/^([a-z]*)(\*){0,1}(\d)([a-z]*)([.|>])/';
$this->vowels = 'aeiouy';
$this->leastLettersIfFirstIsVowel = 2;
$this->leastLettersIfFirstIsConsonant = 3;
$this->rules = array(
'ai*2.', # { -ia > - if intact }
'a*1.', # { -a > - if intact }
'bb1.', # { -bb > -b }
'city3s.', # { -ytic > -ys }
'ci2>', # { -ic > - }
'cn1t>', # { -nc > -nt }
'dd1.', # { -dd > -d }
'dei3y>', # { -ied > -y }
'deec2ss.', # { -ceed > -cess }
'dee1.', # { -eed > -ee }
'de2>', # { -ed > - }
'dooh4>', # { -hood > - }
'e1>', # { -e > - }
'feil1v.', # { -lief > -liev }
'fi2>', # { -if > - }
'gni3>', # { -ing > - }
'gai3y.', # { -iag > -y }
'ga2>', # { -ag > - }
'gg1.', # { -gg > -g }
'ht*2.', # { -th > - if intact }
'hsiug5ct.', # { -guish > -ct }
'hsi3>', # { -ish > - }
'i*1.', # { -i > - if intact }
'i1y>', # { -i > -y }
'@i1d.', # { -i@ > -id -- see nois4@> & vis3@> }
'juf1s.', # { -fuj > -fus }
'ju1d.', # { -uj > -ud }
'jo1d.', # { -oj > -od }
'jeh1r.', # { -hej > -her }
'jrev1t.', # { -verj > -vert }
'jsim2t.', # { -misj > -mit }
'jn1d.', # { -nj > -nd }
'j1s.', # { -j > -s }
'lbaifi6.', # { -ifiabl > - }
'lbai4y.', # { -iabl > -y }
'lba3>', # { -abl > - }
'lbi3.', # { -ibl > - }
'lib2l>', # { -bil > -bl }
'lc1.', # { -cl > c }
'lufi4y.', # { -iful > -y }
'luf3>', # { -ful > - }
'lu2.', # { -ul > - }
'lai3>', # { -ial > - }
'lau3>', # { -ual > - }
'la2>', # { -al > - }
'll1.', # { -ll > -l }
'mui3.', # { -ium > - }
'mu*2.', # { -um > - if intact }
'msi3>', # { -ism > - }
'mm1.', # { -mm > -m }
'nois4@>', # { -sion > -@ }
'noix4ct.', # { -xion > -ct }
'noi3>', # { -ion > - }
'nai3>', # { -ian > - }
'na2>', # { -an > - }
'nee0.', # { protect -een }
'ne2>', # { -en > - }
'nn1.', # { -nn > -n }
'pihs4>', # { -ship > - }
'pp1.', # { -pp > -p }
're2>', # { -er > - }
'rae0.', # { protect -ear }
'ra2.', # { -ar > - }
'ro2>', # { -or > - }
'ru2>', # { -ur > - }
'rr1.', # { -rr > -r }
'rt1>', # { -tr > -t }
'rei3y>', # { -ier > -y }
'sei3y>', # { -ies > -y }
'sis2.', # { -sis > -s }
'si2>', # { -is > - }
'ssen4>', # { -ness > - }
'ss0.', # { protect -ss }
'suo3>', # { -ous > - }
'su*2.', # { -us > - if intact }
's*1>', # { -s > - if intact }
's0.', # { -s > -s }
'tacilp4y.', # { -plicat > -ply }
'ta2>', # { -at > - }
'tnem4>', # { -ment > - }
'tne3>', # { -ent > - }
'tna3>', # { -ant > - }
'tpir2b.', # { -ript > -rib }
'tpro2b.', # { -orpt > -orb }
'tcud1.', # { -duct > -duc }
'tpmus2.', # { -sumpt > -sum }
'tpec2iv.', # { -cept > -ceiv }
'tulo2v.', # { -olut > -olv }
'tsis0.', # { protect -sist }
'tsi3>', # { -ist > - }
'tt1.', # { -tt > -t }
'uqi3.', # { -iqu > - }
'ugo1.', # { -ogu > -og }
'vis3@>', # { -siv > -@ }
'vie0.', # { protect -eiv }
'vi2>', # { -iv > - }
'ylb1>', # { -bly > -bl }
'yli3y>', # { -ily > -y }
'ylp0.', # { protect -ply }
'yl2>', # { -ly > - }
'ygo1.', # { -ogy > -og }
'yhp1.', # { -phy > -ph }
'ymo1.', # { -omy > -om }
'ypo1.', # { -opy > -op }
'yti3>', # { -ity > - }
'yte3>', # { -ety > - }
'ytl2.', # { -lty > -l }
'yrtsi5.', # { -istry > - }
'yra3>', # { -ary > - }
'yro3>', # { -ory > - }
'yfi3.', # { -ify > - }
'ycn2t>', # { -ncy > -nt }
'yca3>', # { -acy > - }
'zi2>', # { -iz > - }
'zy1s.', # { -yz > -ys }
'end0.' # end rule: the stem has already been found
);
$this->stopWords = array(
'a',
'about',
'an',
'are',
'as',
'at',
'be',
'by',
'com',
'de',
'en',
'for',
'from',
'how',
'i',
'in',
'is',
'it',
'la',
'my',
'of',
'on',
'or',
'that',
'the',
'this',
'to',
'very',
'was',
'what',
'when',
'where',
'who',
'will',
'with',
'und',
'the',
'www',
);
}
}[/php]
Je vous laisse faire mumuse avec ça ;) en sachant que c'est forcément toujours incomplet, je pense qu'il manquera toujours un stop-word ou une règle. Le but dans ces système c'est de choisir à partir de quel moment on s'arrête.