手把手教你做關鍵詞匹配項目(搜索引擎)---- 第二十一天


客串:屌絲的坑人表單神器數據庫那點事兒

面向對象升華:面向對象的認識----新生的初識面向對象的番外----思想的夢游篇(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裙寬衣裙");

小帥帥意淫着,想到總有一天把你們踩在腳下。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM