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:
parent
fb502e2f60
commit
5f5c5b124b
5 changed files with 49789 additions and 49758 deletions
|
@ -17,5 +17,8 @@
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.3.3"
|
"php": ">=5.3.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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')) {
|
||||||
|
|
99346
tests/dict.php
99346
tests/dict.php
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue