From 3fd37b5debac47acbf67c531182a9a4ecd7d8ae7 Mon Sep 17 00:00:00 2001
From: Roman Parpalak <roman@parpalak.com>
Date: Sat, 24 Aug 2024 16:14:13 +0300
Subject: [PATCH] Refinements in Russian stemmer.

---
 src/S2/Rose/Stemmer/PorterStemmerRussian.php | 108 +++++++++----------
 tests/unit/Rose/Stemmer/StemmerTest.php      |  19 ++--
 2 files changed, 65 insertions(+), 62 deletions(-)

diff --git a/src/S2/Rose/Stemmer/PorterStemmerRussian.php b/src/S2/Rose/Stemmer/PorterStemmerRussian.php
index 61b5ed3..d986cb6 100644
--- a/src/S2/Rose/Stemmer/PorterStemmerRussian.php
+++ b/src/S2/Rose/Stemmer/PorterStemmerRussian.php
@@ -25,6 +25,7 @@ class PorterStemmerRussian extends AbstractStemmer implements StemmerInterface
         'когда'   => '',
         'где'     => '',
         'куда'    => '',
+        'откуда'  => '',
         'если'    => '',
         'тире'    => '',
         'после'   => '',
@@ -90,7 +91,6 @@ class PorterStemmerRussian extends AbstractStemmer implements StemmerInterface
         'просто'  => '',
         'почему'  => '',
         'потому'  => '',
-        'всего'   => '',
         'чтоб'    => '',
         'чтобы'   => 'чтоб',
         'лишь'    => '',
@@ -107,6 +107,10 @@ class PorterStemmerRussian extends AbstractStemmer implements StemmerInterface
         'из'    => '',
         'из-за' => '',
 
+        'всего' => 'все',
+        'всему' => 'все',
+        'всем'  => 'все',
+
         'что'  => '',
         'чего' => 'что',
         'чему' => 'что',
@@ -131,56 +135,30 @@ class PorterStemmerRussian extends AbstractStemmer implements StemmerInterface
         'никем'  => 'никто',
         'ником'  => 'никто',
 
-        'все-таки'     => '',
-        'во-первых'    => '',
-        'во-вторых'    => '',
-        'в-третьих'    => '',
-
-        // Some popular prefixes and postfixes. TODO come up with a more systematic approach
-        'как-то'       => '',
-        'как-нибудь'   => '',
-        'где-то'       => '',
-        'когда-то'     => '',
-        'когда-нибудь' => '',
-        'куда-то'      => '',
-        'почему-то'    => '',
-        'вообще-то'    => '',
-
-        'что-то'  => 'что-то',
-        'чего-то' => 'что-то',
-        'чему-то' => 'что-то',
-        'чем-то'  => 'что-то',
-        'чём-то'  => 'что-то',
-
-        'что-нибудь'  => 'что-нибудь',
-        'чего-нибудь' => 'что-нибудь',
-        'чему-нибудь' => 'что-нибудь',
-        'чем-нибудь'  => 'что-нибудь',
-        'чём-нибудь'  => 'что-нибудь',
-
-        'кое-что'  => 'кое-что',
-        'кое-чего' => 'кое-что',
-        'кое-чему' => 'кое-что',
-        'кое-чем'  => 'кое-что',
-        'кое-чём'  => 'кое-что',
-
-        'кто-то'  => 'кто-то',
-        'кого-то' => 'кто-то',
-        'кому-то' => 'кто-то',
-        'кем-то'  => 'кто-то',
-        'ком-то'  => 'кто-то',
-
-        'кто-нибудь'  => 'кто-нибудь',
-        'кого-нибудь' => 'кто-нибудь',
-        'кому-нибудь' => 'кто-нибудь',
-        'кем-нибудь'  => 'кто-нибудь',
-        'ком-нибудь'  => 'кто-нибудь',
-
-        'кое-кто'  => 'кое-кто',
-        'кое-кого' => 'кое-кто',
-        'кое-кому' => 'кое-кто',
-        'кое-кем'  => 'кое-кто',
-        'кое-ком'  => 'кое-кто',
+        // Не могу добавить тем и том из-за существительных
+        'того'   => 'тот',
+        'тому'   => 'тот',
+
+        'чей'   => '',
+        'чьего' => 'чей',
+        'чьё'   => 'чей',
+        'чье'   => 'чей',
+        'чья'   => 'чей',
+        'чьи'   => 'чей',
+        'чьей'  => 'чей',
+        'чьих'  => 'чей',
+        'чьему' => 'чей',
+        'чьим'  => 'чей',
+        'чью'   => 'чей',
+        'чьею'  => 'чей',
+        'чьими' => 'чей',
+        'чьём'  => 'чей',
+        'чьем'  => 'чей',
+
+        'все-таки'  => '',
+        'во-первых' => '',
+        'во-вторых' => '',
+        'в-третьих' => '',
 
         'печать' => 'печат', // стеммер считает, что это глагол
 
@@ -320,6 +298,18 @@ class PorterStemmerRussian extends AbstractStemmer implements StemmerInterface
         'могло'  => 'мочь',
         'могли'  => 'мочь',
 
+        'быть'   => '',
+        'был'    => 'быть',
+        'была'   => 'быть',
+        'было'   => 'быть',
+        'были'   => 'быть',
+        'буду'   => 'быть',
+        'будешь' => 'быть',
+        'будет'  => 'быть',
+        'будем'  => 'быть',
+        'будете' => 'быть',
+        'будут'  => 'быть',
+
         'другой'  => 'друго',
         'другого' => 'друго',
         'другому' => 'друго',
@@ -343,11 +333,17 @@ public function stemWord(string $word, bool $normalize = true): string
             return $this->cache[$word];
         }
 
-        /**
-         * TODO How to deal with postfixes like "кто-либо" -> "кого-либо"?
-         * Ignoring postfix is not an option - there are a lot of trash results found.
-         */
-        // $word = preg_replace('/^(.*)-(то|либо|нибудь)$/Su', '-\\2-\\1', $word);
+        if (\preg_match('/^([^\-]+)-(то|либо|нибудь|ка)$/Su', $word, $matches)) {
+            return $this->cache[$word] = $this->stemWord($matches[1]) . '-' . $matches[2];
+        }
+
+        if (substr($word, 0, strlen('кое-')) === 'кое-') {
+            return $this->cache[$word] = 'кое-' . $this->stemWord(substr($word, strlen('кое-')));
+        }
+
+        if (substr($word, 0, strlen('по-')) === 'по-') {
+            return $this->cache[$word] = $word;
+        }
 
         if (isset(self::$irregularWords[$word])) {
             return $this->cache[$word] = (self::$irregularWords[$word] !== '' ? self::$irregularWords[$word] : $word);
diff --git a/tests/unit/Rose/Stemmer/StemmerTest.php b/tests/unit/Rose/Stemmer/StemmerTest.php
index 4333fc1..9b25a1a 100644
--- a/tests/unit/Rose/Stemmer/StemmerTest.php
+++ b/tests/unit/Rose/Stemmer/StemmerTest.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * @copyright 2016-2023 Roman Parpalak
+ * @copyright 2016-2024 Roman Parpalak
  * @license   MIT
  */
 
@@ -54,6 +54,18 @@ public function testRegexes(): void
         $this->assertEquals('доб', $this->russianStemmer->stemWord('добившись'));
     }
 
+    public function testParticles(): void
+    {
+        $this->assertEquals('кто-нибудь', $this->russianStemmer->stemWord('кого-нибудь'));
+        $this->assertEquals('когда-нибудь', $this->russianStemmer->stemWord('когда-нибудь'));
+        $this->assertEquals('что-то', $this->russianStemmer->stemWord('чему-то'));
+        $this->assertEquals('нехитр-то', $this->russianStemmer->stemWord('нехитрое-то'));
+        $this->assertEquals('когда-либо', $this->russianStemmer->stemWord('когда-либо'));
+        $this->assertEquals('что-либо', $this->russianStemmer->stemWord('чем-либо'));
+        $this->assertEquals('кое-что', $this->russianStemmer->stemWord('кое-чем'));
+        $this->assertEquals('кое-кто', $this->russianStemmer->stemWord('кое-кого'));
+    }
+
     public function testStem(): void
     {
         $this->assertEquals('ухмыляться', $this->englishStemmer->stemWord('ухмыляться'));
@@ -65,11 +77,6 @@ public function testStem(): void
 
         $this->assertEquals('метро', $this->russianStemmer->stemWord('метро'));
 
-        $this->assertEquals('кое-кто', $this->russianStemmer->stemWord('кое-кого'));
-        $this->assertEquals('чем-либ', $this->russianStemmer->stemWord('чем-либо'));
-        $this->assertEquals('когда-нибудь', $this->russianStemmer->stemWord('когда-нибудь'));
-        $this->assertEquals('нехитрое-т', $this->russianStemmer->stemWord('нехитрое-то'));
-
         $this->assertEquals('экзамен', $this->russianStemmer->stemWord('экзамен'));
         $this->assertEquals('экзамен', $this->russianStemmer->stemWord('экзамена'));
         $this->assertEquals('экзамен', $this->russianStemmer->stemWord('экзамену'));