CaseMan.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. <?php
  2. namespace App\Tools;
  3. use App\Models\UserDict;
  4. use App\Models\WordIndex;
  5. class CaseMan
  6. {
  7. /**
  8. * Create a new class instance.
  9. *
  10. * @return void
  11. */
  12. public function __construct() {}
  13. /**
  14. * 从词干到单词的变化
  15. *
  16. * @return void
  17. */
  18. public function Declension($base, $type = null, $grammar = '', $confidence = 0.5)
  19. {
  20. $newWord = [];
  21. $case = new CaseEnding;
  22. foreach ($case->ending as $ending) {
  23. // code...
  24. if ($ending[4] < $confidence) {
  25. continue;
  26. }
  27. switch ($type) {
  28. case '.n:base.':
  29. if ($ending[2] !== '.n.' || strpos($ending[3], $grammar) !== 0) {
  30. continue 2;
  31. }
  32. break;
  33. case '.ti:base.':
  34. if ($ending[2] !== '.ti.' && $ending[2] !== '.n.') {
  35. continue 2;
  36. }
  37. break;
  38. case '.adj:base.':
  39. if ($ending[2] !== '.ti.' && $ending[2] !== '.n.') {
  40. continue 2;
  41. }
  42. break;
  43. case '.v:base.':
  44. if ($ending[2] !== '.v.') {
  45. continue 2;
  46. }
  47. break;
  48. default:
  49. continue 2;
  50. break;
  51. }
  52. $endingLen = mb_strlen($ending[0], 'UTF-8');
  53. $wordEnd = mb_substr($base, 0 - $endingLen, null, 'UTF-8');
  54. if ($wordEnd === $ending[0]) {
  55. // 匹配成功
  56. $word = mb_substr($base, 0, mb_strlen($base, 'UTF-8') - $endingLen, 'UTF-8').$ending[1];
  57. // 尝试sandhi
  58. // TODO 加两个sandhi
  59. $hasSandhi = false;
  60. foreach ($case->union as $sandhi) {
  61. $sandhiLen = mb_strlen($sandhi[0], 'UTF-8');
  62. $sandhiEnd = mb_substr($word, 0 - $sandhiLen, null, 'UTF-8');
  63. if ($sandhiEnd === $sandhi[0]) {
  64. $sandhiWord = mb_substr($word, 0, mb_strlen($word, 'UTF-8') - $sandhiLen, 'UTF-8').$sandhi[1];
  65. $count = WordIndex::where('word', $sandhiWord)->select(['count', 'bold'])->first();
  66. if ($count) {
  67. $hasSandhi = true;
  68. $newWord[] = [
  69. 'word' => $sandhiWord,
  70. 'ending' => $ending[1],
  71. 'type' => '.un.',
  72. 'grammar' => '',
  73. 'factors' => "{$word}+{$sandhi[2]}",
  74. 'count' => $count->count,
  75. 'bold' => $count->bold,
  76. ];
  77. // 添加一个去掉ti的数据
  78. if ($sandhi[2] === 'iti') {
  79. $newWord[] = [
  80. 'word' => mb_substr($sandhiWord, 0, -2, 'UTF-8'),
  81. 'ending' => $ending[1],
  82. 'grammar' => $ending[3],
  83. 'factors' => "{$base}+[{$ending[1]}]",
  84. 'count' => $count->count,
  85. 'bold' => $count->bold,
  86. ];
  87. }
  88. }
  89. }
  90. }
  91. $count = WordIndex::where('word', $word)->select(['count', 'bold'])->first();
  92. if ($count || $hasSandhi) {
  93. $newWord[] = [
  94. 'word' => $word,
  95. 'ending' => $ending[1],
  96. 'grammar' => $ending[3],
  97. 'factors' => "{$base}+[{$ending[1]}]",
  98. 'count' => $count ? $count->count : 0,
  99. 'bold' => $count ? $count->bold : 0,
  100. ];
  101. }
  102. }
  103. }
  104. return $newWord;
  105. }
  106. private function endingMatch($base, $ending, $array = null)
  107. {
  108. $case = new CaseEnding;
  109. $output = [];
  110. $endingLen = mb_strlen($ending[0], 'UTF-8');
  111. $wordEnd = mb_substr($base, 0 - $endingLen, null, 'UTF-8');
  112. if ($wordEnd === $ending[0]) {
  113. // 匹配成功
  114. $word = mb_substr($base, 0, mb_strlen($base, 'UTF-8') - $endingLen, 'UTF-8').$ending[1];
  115. if (is_array($array)) {
  116. if (! isset($array[$word])) {
  117. $count = WordIndex::where('word', $word)->select(['count', 'bold'])->first();
  118. }
  119. } else {
  120. $count = WordIndex::where('word', $word)->select(['count', 'bold'])->first();
  121. }
  122. if (isset($count) && $count) {
  123. $output[$word] = ['count' => $count->count, 'bold' => $count->bold];
  124. } else {
  125. $output[$word] = false;
  126. }
  127. // 尝试sandhi
  128. // TODO 加两个sandhi
  129. foreach ($case->union as $sandhi) {
  130. $sandhiLen = strlen($sandhi[0]);
  131. $sandhiEnd = mb_substr($word, 0 - $sandhiLen, null, 'UTF-8');
  132. if ($sandhiEnd === $sandhi[0]) {
  133. $sandhiWord = mb_substr($word, 0, mb_strlen($word, 'UTF-8') - $sandhiLen, 'UTF-8').$sandhi[1];
  134. if (is_array($array)) {
  135. if (! isset($array[$sandhiWord])) {
  136. $count = WordIndex::where('word', $sandhiWord)->select(['count', 'bold'])->first();
  137. }
  138. } else {
  139. $count = WordIndex::where('word', $sandhiWord)->select(['count', 'bold'])->first();
  140. }
  141. if (isset($count) && $count) {
  142. $output[$sandhiWord] = ['count' => $count->count, 'bold' => $count->bold];
  143. } else {
  144. $output[$sandhiWord] = false;
  145. }
  146. }
  147. }
  148. }
  149. return $output;
  150. }
  151. /**
  152. * 从词干到单词的变化
  153. *
  154. * @return array
  155. */
  156. public function BaseToWord($base, $confidence = 0.5)
  157. {
  158. $newWord = [];
  159. $case = new CaseEnding;
  160. foreach ($case->ending as $ending) {
  161. // code...
  162. if ($ending[4] < $confidence) {
  163. continue;
  164. }
  165. /*
  166. $matched = $this->endingMatch($base,$ending,$newWord);
  167. foreach ($matched as $key => $new) {
  168. $newWord[$key] = $new;
  169. }
  170. */
  171. $endingLen = mb_strlen($ending[0], 'UTF-8');
  172. $wordEnd = mb_substr($base, 0 - $endingLen, null, 'UTF-8');
  173. if ($wordEnd === $ending[0]) {
  174. // 匹配成功
  175. $word = mb_substr($base, 0, mb_strlen($base, 'UTF-8') - $endingLen, 'UTF-8').$ending[1];
  176. if (! isset($newWord[$word])) {
  177. $count = WordIndex::where('word', $word)->select(['count', 'bold'])->first();
  178. if ($count) {
  179. $newWord[$word] = ['count' => $count->count, 'bold' => $count->bold];
  180. } else {
  181. $newWord[$word] = false;
  182. }
  183. }
  184. // 尝试sandhi
  185. // TODO 加两个sandhi
  186. foreach ($case->union as $sandhi) {
  187. $sandhiLen = mb_strlen($sandhi[0], 'UTF-8');
  188. $sandhiEnd = mb_substr($word, 0 - $sandhiLen, null, 'UTF-8');
  189. if ($sandhiEnd === $sandhi[0]) {
  190. $sandhiWord = mb_substr($word, 0, mb_strlen($word, 'UTF-8') - $sandhiLen, 'UTF-8').$sandhi[1];
  191. if (! isset($newWord[$sandhiWord])) {
  192. $count = WordIndex::where('word', $sandhiWord)->select(['count', 'bold'])->first();
  193. if ($count) {
  194. $newWord[$sandhiWord] = ['count' => $count->count, 'bold' => $count->bold];
  195. } else {
  196. $newWord[$sandhiWord] = false;
  197. }
  198. }
  199. }
  200. }
  201. }
  202. }
  203. $result = [];
  204. foreach ($newWord as $key => $value) {
  205. // code...
  206. if ($value !== false) {
  207. $result[] = ['word' => $key, 'ending', 'count' => $value['count'], 'bold' => $value['bold']];
  208. }
  209. }
  210. return $result;
  211. }
  212. /**
  213. * 从单词到词干的变化
  214. * 小蝌蚪找妈妈
  215. *
  216. * @param string $word 输入
  217. * @param int $deep 搜索深度
  218. * @param bool $verify 是否验证单词存在
  219. * @return array
  220. */
  221. public function WordToBase($word, $deep = 1, $verify = true)
  222. {
  223. $newWords = [];
  224. $newBase = [];
  225. $input[$word] = true;
  226. $case = new CaseEnding;
  227. for ($i = 0; $i < $deep; $i++) {
  228. // code...
  229. foreach ($input as $currWord => $status) {
  230. // code...
  231. if ($status) {
  232. $input[$currWord] = false;
  233. foreach ($case->ending as $ending) {
  234. // code...
  235. if ($ending[4] < 0.5) {
  236. continue;
  237. }
  238. $endingLen = mb_strlen($ending[1], 'UTF-8');
  239. $wordEnd = mb_substr($currWord, 0 - $endingLen, null, 'UTF-8');
  240. if ($wordEnd === $ending[1]) {
  241. // 匹配成功
  242. $base = mb_substr($currWord, 0, mb_strlen($currWord, 'UTF-8') - $endingLen, 'UTF-8').$ending[0];
  243. if (! isset($newBase[$base])) {
  244. $newBase[$base] = [];
  245. }
  246. $info = [
  247. 'word' => $currWord,
  248. 'type' => $ending[2],
  249. 'grammar' => $ending[3],
  250. 'parent' => $base,
  251. 'factors' => "{$base}+[{$ending[1]}]",
  252. 'confidence' => $ending[4],
  253. ];
  254. array_push($newBase[$base], $info);
  255. if ($ending[2] === '.n.') {
  256. $info['type'] = '.ti.';
  257. array_push($newBase[$base], $info);
  258. $info['type'] = '.adj.';
  259. array_push($newBase[$base], $info);
  260. }
  261. if ($ending[2] === '.ti.') {
  262. $info['type'] = '.adj.';
  263. array_push($newBase[$base], $info);
  264. }
  265. }
  266. }
  267. }
  268. }
  269. foreach ($newBase as $currWord => $value) {
  270. // 把新词加入列表
  271. if (! isset($input[$currWord])) {
  272. $input[$currWord] = true;
  273. }
  274. }
  275. }
  276. if ($verify) {
  277. $output = [];
  278. foreach ($newBase as $base => $rows) {
  279. // code...
  280. if (($verify = $this->VerifyBase($base, $rows)) !== false) {
  281. $output[$base] = $verify;
  282. }
  283. }
  284. if (count($output) == 0) {
  285. // 如果验证失败 输出最可能的结果
  286. $short = 10000;
  287. $shortBase = '';
  288. foreach ($newBase as $base => $rows) {
  289. if (mb_strlen($base, 'UTF-8') < $short) {
  290. $short = mb_strlen($base, 'UTF-8');
  291. $shortBase = $base;
  292. }
  293. }
  294. foreach ($newBase as $base => $rows) {
  295. if ($base == $shortBase) {
  296. $output[$base] = $rows;
  297. }
  298. }
  299. }
  300. return $output;
  301. } else {
  302. return $newBase;
  303. }
  304. }
  305. /**
  306. * 验证base在字典中是否存在
  307. */
  308. public function VerifyBase($base, $rows)
  309. {
  310. //
  311. $output = [];
  312. $dictWords = UserDict::where('word', $base)
  313. ->select(['type', 'grammar'])
  314. ->groupBy(['type', 'grammar'])
  315. ->get();
  316. if (count($dictWords) > 0) {
  317. $newBase[$base] = 1;
  318. $case = [];
  319. // 字典中这个拼写的单词的语法信息
  320. foreach ($dictWords as $value) {
  321. if ($value->type === '.n.') {
  322. $arrGrammar = explode('$', $value->grammar);
  323. $case[$value->type.$arrGrammar[0]] = 1;
  324. } else {
  325. $case[$value->type] = 1;
  326. }
  327. }
  328. foreach ($rows as $value) {
  329. // 根据输入的猜测的type,grammar拼接合理的 parent 语法信息
  330. switch ($value['type']) {
  331. case '.n.':
  332. $parentType = '.n:base.';
  333. break;
  334. case '.ti.':
  335. $parentType = '.ti:base.';
  336. break;
  337. case '.v.':
  338. $parentType = '.v:base.';
  339. break;
  340. default:
  341. $parentType = '';
  342. break;
  343. }
  344. if (! empty($value['grammar']) && $value['type'] === '.n.') {
  345. $arrGrammar = explode('$', $value['grammar']);
  346. $parentType .= $arrGrammar[0];
  347. }
  348. // 只保存语法信息合理的数据
  349. if (isset($case[$parentType])) {
  350. array_push($output, $value);
  351. }
  352. }
  353. return $output;
  354. } else {
  355. return false;
  356. }
  357. }
  358. }