面向對象升華:面向對象的認識----新生的初識、面向對象的番外----思想的夢游篇(1)、面向對象的認識---如何找出類
負載均衡:負載均衡----概念認識篇、負載均衡----實現配置篇(Nginx)
吐槽:現在欠的文章有面向對象的認識----類的轉化、面向對象的番外---思想的夢游篇(2)、負載均衡 ---- 文件服務策略、手把手教你做關鍵詞匹配項目(搜索引擎)。真心太多了,能不能讓我休息一會兒。
第二十一天
起點:手把手教你做關鍵詞匹配項目(搜索引擎)---- 第一天
回顧:手把手教你做關鍵詞匹配項目(搜索引擎)---- 第二十天
今天有個理論知識要理解的,叫做測試驅動編程,之前我提到過概念,在:手把手教你做關鍵詞匹配項目(搜索引擎)---- 第十一天
今天小帥帥秀逗了一回,使用了這個思想。
好了,以下正文開始。
話說小帥帥把自己寫的業務拆詞方法給了於老大看,於老大很高興。
但是業務拆詞的詞組都是有限的,還有就是當業務拆詞的數據量越來越大的時候,就會造成運算時間增加。
於老大就提到,是否可以用其它分詞擴展來彌補拆詞的不足。
畢竟人家專業人士做的,比較靠譜點。
於老大很有經驗,就推薦小帥帥去了解SCWS的用法.
SCWS 是 Simple Chinese Word Segmentation 的首字母縮寫(即:簡易中文分詞系統)。
官方網址:http://www.xunsearch.com/scws/index.php
小帥帥聽了當然很開心羅,因為又有新的知識點了。
小帥帥照着SCWS的安裝文檔安裝了SCWS。
並把php擴展裝好了,並嘗試寫了個測試代碼:
<?php class TestSCWS { public static function split($keyword){ if (!extension_loaded("scws")) { throw new Exception("scws extension load fail"); } $so = scws_new(); $so->set_charset('utf8'); $so->send_text($keyword); $ret = array(); while ($res = $so->get_result()) { foreach ($res as $tmp) { if (self::isValidate($tmp)) { $ret[] = $tmp; } } } $so->close(); return $ret; } public static function isValidate($scws_words) { if ($scws_words['len'] == 1 && ($scws_words['word'] == "\r" || $scws_words['word'] == "\n")) { return false; } return true; } } var_dump(TestSCWS::split("連衣裙xxl裙連衣裙"));
測試通過,跟理想中的一摸一樣,小帥帥很高興,就去問於老大:於老大我會用SCWS了,下一步該怎么辦?
於老大也不慌,就對小帥帥說: 你先寫個ScwsSplitter來拆分關鍵詞吧。
小帥帥非常高興,因為他學到了新的知識,就對於老大說到好的。
小帥帥說到做到,代碼如下:
class ScwsSplitter { public $keyword; public function split(){ if (!extension_loaded("scws")) { throw new Exception("scws extension load fail"); } $keywordEntity = new KeywordEntity($this->keyword); $so = scws_new(); $so->set_charset('utf8'); $so->send_text($this->keyword); while ($res = $so->get_result()) { foreach ($res as $tmp) { if ($this->isValidate($tmp)) { $keywordEntity->addElement($tmp["word"]); } } } $so->close(); return $keywordEntity; } public function isValidate($scws_words) { if ($scws_words['len'] == 1 && ($scws_words['word'] == "\r" || $scws_words['word'] == "\n")) { return false; } return true; } }
小帥帥又跑去找於老大,說到:我把Scws的分詞代碼寫好了。
於老大也佩服小帥帥的高效率。
又說到:如果我兩個同時用了,我先用業務分詞,遺留下來的詞用Scws分詞,小帥帥有好的方案嗎?
小帥帥就問到: 為啥要這樣,這不是多此一舉。
於老大就說到:業務有些專有名詞,SCWS分不出來丫,那怎么辦好?
小帥帥又說到:我看文檔的時候看到有詞庫和規則文件的設置,我們用它好不好?
於老大又說到:這個是可以,但是我們如何保證讓運營人員維護,我們要學會把這些事情交出去丫。
小帥帥: …….
小帥帥沉默了片刻,覺得現在兩個類都寫了,一起用是最快的方案,就答應到:好吧,我回去改改….
首先小帥帥根據測試驅動編程的思想寫了入口代碼:
class SplitterApp { public static function split($keyword,$cid){ $keywordEntity = new KeywordEntity($keyword); #業務分詞 $termSplitter = new TermSplitter($keywordEntity); $seg = new DBSegmentation(); $seg->cid = $cid; $termSplitter->setDictionary($seg->transferDictionary()); $termSplitter->split(); #SCWS分詞 $scwsSplitter = new ScwsSplitter($keywordEntity); $scwsSplitter->split(); #后續遺留單詞或者詞組處理 $elementWords = $keywordEntity->getElementWords(); $remainKeyword = str_replace($elementWords, "::", $keywordEntity->keyword); $remainElements = explode("::", $remainKeyword); foreach($remainElements as $element){ if(!empty($element)) $keywordEntity->addElement($element); }
return $keywordEntity; } }
小帥帥嘿了一聲,有了測試入口,還怕其他的搞不定。
首先KeywordEntity的getElementWords,先搞定他.
class KeywordEntity { public $keyword; public $elements = array(); public function __construct($keyword) { $this->keyword = $keyword; } public function addElement($word, $times = 1) { if (isset($this->elements[$word])) { $this->elements[$word]->times += $times; } else $this->elements[$word] = new KeywordElement($word, $times); } public function getElementWords() { $elementWords = array_keys($this->elements); usort($elementWords, function ($a, $b) { return (UTF8::length($a) < UTF8::length($b)) ? 1 : -1; }); return $elementWords; } /** * @desc 計算UTF8字符串權重 * @param string $word * @return float */ public function calculateWeight($word) { $element = $this->elements[$word]; return ROUND(strlen($element->word) * $element->times / strlen($this->keyword), 3); } } class KeywordElement { public $word; public $times; public function __construct($word, $times) { $this->word = $word; $this->times = $times; } }
其次就是分詞了,首先先抽出公用類先,Splitter變成了公用類,有哪些方法呢?
1. 抽象split方法
2. 獲取關鍵詞待拆分的詞組
3. 是否需要拆分
按照這寫,小帥帥寫出了以下代碼:
abstract class Splitter { /** * @var KeywordEntity $keywordEntity */ public $keywordEntity; public function __construct($keywordEntity){ $this->keywordEntity = $keywordEntity; } public abstract function split(); /** * 獲取未分割的字符串,過濾單詞 * * @return array */ public function getRemainKeywords() { $elementWords = $this->keywordEntity->getElementWords(); $remainKeyword = str_replace($elementWords, "::", $this->keywordEntity->keyword); $remainElements = explode("::", $remainKeyword); $ret = array(); foreach ($remainElements as $element) { if ($this->isSplit($element)) { $ret[] = $element; } } return $ret; } /** * 是否需要拆分 * * @param $element * @return bool */ public function isSplit($element) { if (UTF8::isPhrase($element)) { return true; } return false; } }
然后小帥帥繼續實現業務拆分算法,以及Scws拆分算法。小帥帥淫笑了,這點小事情還是可以辦到的。
class TermSplitter extends Splitter { private $dictionary = array(); public function setDictionary($dictionary = array()) { usort($dictionary, function ($a, $b) { return (UTF8::length($a) < UTF8::length($b)) ? 1 : -1; }); $this->dictionary = $dictionary; } public function getDictionary() { return $this->dictionary; } /** * 把關鍵詞拆分成詞組或者單詞 * * @return KeywordScore[] $keywordScores */ public function split() { foreach ($this->dictionary as $phrase) { $remainKeyword = implode("::",$this->getRemainKeywords()); $matchTimes = preg_match_all("/$phrase/", $remainKeyword, $matches); if ($matchTimes > 0) { $this->keywordEntity->addElement($phrase, $matchTimes); } } } } class ScwsSplitter extends Splitter { public function split() { if (!extension_loaded("scws")) { throw new Exception("scws extension load fail"); } $remainElements = $this->getRemainKeywords(); foreach ($remainElements as $element) { $so = scws_new(); $so->set_charset('utf8'); $so->send_text($element); while ($res = $so->get_result()) { foreach ($res as $tmp) { if ($this->isValidate($tmp)) { $this->keywordEntity->addElement($tmp['word']); } } } $so->close(); } } /** * @param array $scws_words * @return bool */ public function isValidate($scws_words) { if ($scws_words['len'] == 1 && ($scws_words['word'] == "\r" || $scws_words['word'] == "\n")) { return false; } return true; } }
小帥帥終於把這些代碼全部搞定了,高興之余,他還順手畫了UML圖送給大家:
小帥帥的成長真心夠厲害的哦,於老大看后,連稱贊了三次。
為了測試,小帥帥寫了測試代碼,代碼如下:
class SplitterAppTest { public static function split($keyword){ $keywordEntity = new KeywordEntity($keyword); #業務分詞 $termSplitter = new TermSplitter($keywordEntity); $seg = new TestSegmentation(); $termSplitter->setDictionary($seg->transferDictionary()); $termSplitter->split(); #SCWS分詞 $scwsSplitter = new ScwsSplitter($keywordEntity); $scwsSplitter->split(); #后續遺留單詞或者詞組處理 $elementWords = $keywordEntity->getElementWords(); $remainKeyword = str_replace($elementWords, "::", $keywordEntity->keyword); $remainElements = explode("::", $remainKeyword); foreach($remainElements as $element){ if(!empty($element)) $keywordEntity->addElement($element); } return $keywordEntity; } } SplitterAppTest::split("連衣裙xl裙寬衣裙");
小帥帥意淫着,想到總有一天把你們踩在腳下。