All methods are now static

- Restore original mb_internal_encoding after method call
- Add PHPUnit 5.0 to dev-deps
- dict.php wrapped into PHPUnit data provider
- Increased memory limit for tests
This commit is contained in:
Протопопов Валерий 2017-11-07 15:00:49 +07:00
parent fb502e2f60
commit 5f5c5b124b
5 changed files with 49789 additions and 49758 deletions

View file

@ -17,5 +17,8 @@
}, },
"require": { "require": {
"php": ">=5.3.3" "php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "^5.0"
} }
} }

View file

@ -12,126 +12,146 @@ namespace NXP;
class Stemmer class Stemmer
{ {
private $vowel = "аеёиоуыэюя"; const VOWEL = 'аеёиоуыэюя';
private $regexPerfectiveGerunds = array( const REGEX_PERFECTIVE_GERUNDS1 = '(в|вши|вшись)$';
"(в|вши|вшись)$", const REGEX_PERFECTIVE_GERUNDS2 = '(ив|ивши|ившись|ыв|ывши|ывшись)$';
"(ив|ивши|ившись|ыв|ывши|ывшись)$" const REGEX_ADJECTIVE = '(ее|ие|ые|ое|ими|ыми|ей|ий|ый|ой|ем|им|ым|ом|его|ого|ему|ому|их|ых|ую|юю|ая|яя|ою|ею)$';
); const REGEX_PARTICIPLE1 = '(ем|нн|вш|ющ|щ)';
private $regexAdjective = "(ее|ие|ые|ое|ими|ыми|ей|ий|ый|ой|ем|им|ым|ом|его|ого|ему|ому|их|ых|ую|юю|ая|яя|ою|ею)$"; const REGEX_PARTICIPLE2 = '(ивш|ывш|ующ)';
private $regexParticiple = array( const REGEX_REFLEXIVES = '(ся|сь)$';
"(ем|нн|вш|ющ|щ)", const REGEX_VERB1 = '(ла|на|ете|йте|ли|й|л|ем|н|ло|но|ет|ют|ны|ть|ешь|нно)$';
"(ивш|ывш|ующ)" const REGEX_VERB2 = '(ила|ыла|ена|ейте|уйте|ите|или|ыли|ей|уй|ил|ыл|им|ым|ен|ило|ыло|ено|ят|ует|уют|ит|ыт|ены|ить|ыть|ишь|ую|ю)$';
); const REGEX_NOUN = '(а|ев|ов|ие|ье|е|иями|ями|ами|еи|ии|и|ией|ей|ой|ий|й|иям|ям|ием|ем|ам|ом|о|у|ах|иях|ях|ы|ь|ию|ью|ю|ия|ья|я)$';
private $regexReflexives = "(ся|сь)$"; const REGEX_SUPERLATIVE = '(ейш|ейше)$';
private $regexVerb = array( const REGEX_DERIVATIONAL = '(ост|ость)$';
"(ла|на|ете|йте|ли|й|л|ем|н|ло|но|ет|ют|ны|ть|ешь|нно)$", const REGEX_I = 'и$';
"(ила|ыла|ена|ейте|уйте|ите|или|ыли|ей|уй|ил|ыл|им|ым|ен|ило|ыло|ено|ят|ует|уют|ит|ыт|ены|ить|ыть|ишь|ую|ю)$" const REGEX_NN = 'нн$';
); const REGEX_SOFT_SIGN = 'ь$';
private $regexNoun = "(а|ев|ов|ие|ье|е|иями|ями|ами|еи|ии|и|ией|ей|ой|ий|й|иям|ям|ием|ем|ам|ом|о|у|ах|иях|ях|ы|ь|ию|ью|ю|ия|ья|я)$";
private $regexSuperlative = "(ейш|ейше)$";
private $regexDerivational = "(ост|ость)$";
private $regexI = "и$";
private $regexNN = "нн$";
private $regexSoftSign = "ь$";
private $word = ''; /**
private $RV = 0; * @param string $word
private $R2 = 0; *
* @return string
public function getWordBase($word) */
public static function getWordBase($word)
{ {
$originalInternalEncoding = mb_internal_encoding();
mb_internal_encoding('UTF-8'); mb_internal_encoding('UTF-8');
$this->word = $word;
$this->findRegions(); list($rv, $r2) = self::findRegions($word);
//Шаг 1
//Найти окончание PERFECTIVE GERUND. Если оно существует удалить его и завершить этот шаг. // Шаг 1: Найти окончание PERFECTIVE GERUND. Если оно существует удалить его и завершить этот шаг.
if (!$this->removeEndings($this->regexPerfectiveGerunds, $this->RV)) { if (!self::removeEndings($word, array(self::REGEX_PERFECTIVE_GERUNDS1, self::REGEX_PERFECTIVE_GERUNDS2), $rv)) {
//Иначе, удаляем окончание REFLEXIVE (если оно существует). // Иначе, удаляем окончание REFLEXIVE (если оно существует).
$this->removeEndings($this->regexReflexives, $this->RV); self::removeEndings($word, self::REGEX_REFLEXIVES, $rv);
//Затем в следующем порядке пробуем удалить окончания: ADJECTIVAL, VERB, NOUN. Как только одно из них найдено шаг завершается.
if (!($this->removeEndings( // Затем в следующем порядке пробуем удалить окончания: ADJECTIVAL, VERB, NOUN. Как только одно из них найдено шаг завершается.
if (!(self::removeEndings(
$word,
array( array(
$this->regexParticiple[0] . $this->regexAdjective, self::REGEX_PARTICIPLE1 . self::REGEX_ADJECTIVE,
$this->regexParticiple[1] . $this->regexAdjective self::REGEX_PARTICIPLE2 . self::REGEX_ADJECTIVE
), ),
$this->RV $rv
) || $this->removeEndings($this->regexAdjective, $this->RV)) ) || self::removeEndings($word, self::REGEX_ADJECTIVE, $rv))
) { ) {
if (!$this->removeEndings($this->regexVerb, $this->RV)) { if (!self::removeEndings($word, array(self::REGEX_VERB1, self::REGEX_VERB2), $rv)) {
$this->removeEndings($this->regexNoun, $this->RV); self::removeEndings($word, self::REGEX_NOUN, $rv);
} }
} }
} }
//Шаг 2
//Если слово оканчивается на и удаляем и.
$this->removeEndings($this->regexI, $this->RV);
//Шаг 3
//Если в R2 найдется окончание DERIVATIONAL удаляем его.
$this->removeEndings($this->regexDerivational, $this->R2);
//Шаг 4
//Возможен один из трех вариантов:
//Если слово оканчивается на нн удаляем последнюю букву.
if ($this->removeEndings($this->regexNN, $this->RV)) {
$this->word .= 'н';
}
//Если слово оканчивается на SUPERLATIVE удаляем его и снова удаляем последнюю букву, если слово оканчивается на нн.
$this->removeEndings($this->regexSuperlative, $this->RV);
//Если слово оканчивается на ь удаляем его.
$this->removeEndings($this->regexSoftSign, $this->RV);
return $this->word; // Шаг 2: Если слово оканчивается на и удаляем и.
self::removeEndings($word, self::REGEX_I, $rv);
// Шаг 3: Если в R2 найдется окончание DERIVATIONAL удаляем его.
self::removeEndings($word, self::REGEX_DERIVATIONAL, $r2);
// Шаг 4: Возможен один из трех вариантов:
// 1. Если слово оканчивается на нн удаляем последнюю букву.
if (self::removeEndings($word, self::REGEX_NN, $rv)) {
$word .= 'н';
}
// 2. Если слово оканчивается на SUPERLATIVE удаляем его и снова удаляем последнюю букву, если слово оканчивается на нн.
self::removeEndings($word, self::REGEX_SUPERLATIVE, $rv);
// 3. Если слово оканчивается на ь удаляем его.
self::removeEndings($word, self::REGEX_SOFT_SIGN, $rv);
mb_internal_encoding($originalInternalEncoding);
return $word;
} }
public function removeEndings($regex, $region) /**
* @param string $word
* @param string|string[] $regex
* @param int $region
*
* @return bool
*/
public static function removeEndings(&$word, $regex, $region)
{ {
$prefix = mb_substr($this->word, 0, $region, 'utf8'); $prefix = mb_substr($word, 0, $region, 'utf8');
$word = substr($this->word,strlen($prefix)); $ending = substr($word, strlen($prefix));
if (is_array($regex)) { if (is_array($regex)) {
if (preg_match('/.+[а|я]' . $regex[0] . '/u', $word)) { if (preg_match('/.+[а|я]' . $regex[0] . '/u', $ending)) {
$this->word = $prefix . preg_replace('/' . $regex[0] . '/u', '', $word); $word = $prefix . preg_replace('/' . $regex[0] . '/u', '', $ending);
return true; return true;
} }
$regex = $regex[1]; $regex = $regex[1];
} }
if (preg_match('/.+' . $regex . '/u', $word)) { if (preg_match('/.+' . $regex . '/u', $ending)) {
$this->word = $prefix . preg_replace('/' . $regex . '/u', '', $word); $word = $prefix . preg_replace('/' . $regex . '/u', '', $ending);
return true; return true;
} }
return false; return false;
} }
private function findRegions() /**
* @param string $word
*
* @return int[]
*/
private static function findRegions($word)
{ {
$rv = 0;
$state = 0; $state = 0;
$wordLength = mb_strlen($this->word, 'utf8'); $wordLength = mb_strlen($word, 'utf8');
for ($i = 1; $i < $wordLength; $i++) { for ($i = 1; $i < $wordLength; $i++) {
$prevChar = mb_substr($this->word, $i - 1, 1, 'utf8'); $prevChar = mb_substr($word, $i - 1, 1, 'utf8');
$char = mb_substr($this->word, $i, 1, 'utf8'); $char = mb_substr($word, $i, 1, 'utf8');
switch ($state) { switch ($state) {
case 0: case 0:
if ($this->isVowel($char)) { if (self::isVowel($char)) {
$this->RV = $i + 1; $rv = $i + 1;
$state = 1; $state = 1;
} }
break; break;
case 1: case 1:
if ($this->isVowel($prevChar) && !$this->isVowel($char)) { if (self::isVowel($prevChar) && !self::isVowel($char)) {
$state = 2; $state = 2;
} }
break; break;
case 2: case 2:
if ($this->isVowel($prevChar) && !$this->isVowel($char)) { if (self::isVowel($prevChar) && !self::isVowel($char)) {
$this->R2 = $i + 1; return [$rv, $i + 1];
return;
} }
break; break;
} }
} }
return [$rv, 0];
} }
private function isVowel($char) /**
* @param string $char
*
* @return bool
*/
private static function isVowel($char)
{ {
return (strpos($this->vowel, $char) !== false); return strpos(self::VOWEL, $char) !== false;
} }
} }

View file

@ -12,12 +12,19 @@ namespace NXP;
class StemmerTest extends \PHPUnit_Framework_TestCase class StemmerTest extends \PHPUnit_Framework_TestCase
{ {
public function testStemming() public function testStemmingDataProvider()
{ {
$stemmer = new Stemmer(); return require("dict.php");
$testWords = require("dict.php"); }
foreach ($testWords as $word => $base) {
$this->assertEquals($base, $stemmer->getWordBase($word)); /**
} * @dataProvider testStemmingDataProvider
*
* @param string $word
* @param string $expected
*/
public function testStemming($word, $expected)
{
$this->assertEquals($expected, Stemmer::getWordBase($word));
} }
} }

View file

@ -1,5 +1,6 @@
<?php <?php
ini_set('memory_limit', '512M');
$vendorDir = __DIR__ . '/../../..'; $vendorDir = __DIR__ . '/../../..';
if (file_exists($file = $vendorDir . '/autoload.php')) { if (file_exists($file = $vendorDir . '/autoload.php')) {

File diff suppressed because it is too large Load diff