敏感詞檢測 評論敏感詞過濾 DFA算法 AC自動機

DFA,全稱 Deterministic Finite Automaton 即確定有窮自動機:從一個狀態(tài)通過一系列的事件轉(zhuǎn)換到另一個狀態(tài),即 state -> event -> state。

確定:狀態(tài)以及引起狀態(tài)轉(zhuǎn)換的事件都是可確定的,不存在“意外”。
有窮:狀態(tài)以及事件的數(shù)量都是可窮舉的。
計算機操作系統(tǒng)中的進程狀態(tài)與切換可以作為 DFA 算法的一種近似理解。如下圖所示,其中橢圓表示狀態(tài),狀態(tài)之間的連線表示事件,進程的狀態(tài)以及事件都是可確定的,且都可以窮舉。

<?php
namespace Module\Vendor\File;

use Module\Vendor\BaseVendor;

/**
 * Created by PhpStorm.
 * Auth: aoshi
 * Date: 2021/04/28
 * Time: 13:01
 * 敏感詞過濾器
 * public 方法
 *      checkText:敏感詞匹配檢測
 *      addKeywords:添加敏感詞
 *      clearkeywors:清空敏感詞
 * 描述: 兼容單字符關(guān)鍵詞、重復(fù)關(guān)鍵詞、關(guān)鍵詞包含關(guān)系 例如: 青 青年 中國 中國青年 青年 國青 年  單字符:清 年 重復(fù)關(guān)鍵詞:青年  包含關(guān)系:中國青年  中國  青年
 * 支持無效字符剔除
 * 只要命中過濾詞 每一次命中都會被反饋,反饋格式: 關(guān)鍵詞 開始位置  結(jié)束位置
 */

/**
 *
 *
 *
 * */
class GreenFilterVender extends BaseVendor {

    protected $nodes;       //敏感詞節(jié)點列表
    protected $invalidChar = array(',',';',';',',','-','"','“','、');     //無效字符

    public function __construct()
    {
        parent::__construct();
        $this->_initialize();
    }

    /**
     * 對象關(guān)鍵屬性初始化
     * */
    protected function _initialize() {
        $this->nodes = $this->getKeywords();
    }

    /**
     * 敏感詞匹配檢測 兼容單個詞過濾
     * @param   string      要搜索的關(guān)鍵詞
     * @return  mixed       false:未命中 array:命中的關(guān)鍵詞
     * */
    public function checkText($keyWord) {
        if(!$keyWord) {
            return false;
        }

        //過濾無效字符
        if($this->invalidChar) {
            $keyWord = str_replace($this->invalidChar,'',$keyWord);   
        }
        $charList = $this->get_chars($keyWord);
        $nodes = $this->nodes;
        $charNum = count($charList);
        $nodeKey = 0;
        $nodeLevel = 0;
        $foundList = array();
        for($key = 0;$key < $charNum;$key++) {
            $val = $charList[$key];
            if($nodeKey == 0) {     //命中首個敏感關(guān)鍵詞
                if(in_array($val,array_keys($nodes[$nodeKey]))) {
                    $nodeKey = $nodes[$nodeKey][$val];
                }
            } else {
                if(in_array($val,array_keys($nodes[$nodeKey]))) {
                    if(in_array('',array_keys($nodes[$nodeKey]))) {     //單字符匹配  存在空字符key 就滿足
                        $matchkey = $nodes[$nodeKey][''];
                        $foundList[] = array($matchkey['keyword'],$key-1,$key);
                    }
                    $matchkey = $nodes[$nodeKey][$val];
                    $nodeLevel = $matchkey['level'];     //當前判斷深度 回滾需要
                    if(isset($matchkey['is_end']) && $matchkey['is_end'] == 1) {        //存在末尾結(jié)束符
                        $foundList[] = array($matchkey['keyword'],$key-count($matchkey['keyword']),$key+1);             //映射數(shù)據(jù)結(jié)構(gòu) array(命中次,開始位置,結(jié)束位置)
                        if(isset($matchkey['next_pointer']) && $matchkey['next_pointer']) {     //如果還有多余的字符
                            $nodeKey = $matchkey['next_pointer'];
                        } else {        //命中敏感詞結(jié)束 回退到關(guān)鍵詞開始的第二個位置
                            $key = $key - $nodeLevel;
                            $nodeLevel = 0;
                            $nodeKey = 0;
                        }
                    } else {
                        if(isset($matchkey['next_pointer']) && $matchkey['next_pointer']) {     //沒有結(jié)束符 繼續(xù)匹配
                            $nodeKey = $matchkey['next_pointer'];
                        }
                    }
                } else {
                    if(in_array('',array_keys($nodes[$nodeKey]))) {     //單字符匹配
                        $matchkey = $nodes[$nodeKey][''];
                        $foundList[] = array($matchkey['keyword'],$key-1,$key);
                    }
                    if($nodeLevel) {        //命中首字母 沒有命中全部 回退多減1 (多了一次無效循環(huán))
                        $key = $key - $nodeLevel - 1;
                    }
                    $nodeLevel = 0;
                    $nodeKey = 0;
                }
            }
        }

        return $foundList;
    }

    /**
     * 添加敏感詞 可以考慮儲存到redis
     * @param   array|string    關(guān)鍵詞
     * @return  bool    false:失敗  true:成功
     * */
    public function addKeywords($keyWords) {
        if(!is_array($keyWords)) {
            $keyWords = array($keyWords);
        }
        $nodes = $this->nodes;
//        is_end:終止符號 keyword:關(guān)鍵詞  next_pointer:下一個指針  level:層次
        foreach($keyWords as $keyWord) {
            $charList = $this->get_chars($keyWord);
            $lastKey = count($charList) - 1;
            foreach($charList as $key => $val) {
                if($key == 0) {        //過濾詞的第一個元素 添加一個元素
                    if(!in_array($val,array_keys($nodes[0]))) {
                        $nodeKey = count($nodes);
                        $nodes[$nodeKey] = array();
                        $nodes[0][$val] = $nodeKey;
                        if($key == $lastKey) {      //首字符不存在  單字符過濾
                            if(!isset($nodes[$nodeKey][''])) {      //創(chuàng)建單字符終結(jié)
                                $nodes[$nodeKey]['']['is_end'] = 1;
                                $nodes[$nodeKey]['']['level'] = $key;
                                $nodes[$nodeKey]['']['keyword'] = $keyWord;
                            }
                        }
                    } else {
                        $nodeKey = $nodes[0][$val];
                        if($key == $lastKey) {      //首字符存在 單字符過濾
                            if(!isset($nodes[$nodeKey][''])) {      //創(chuàng)建單字符終結(jié)
                                $nodes[$nodeKey]['']['is_end'] = 1;
                                $nodes[$nodeKey]['']['level'] = $key;
                                $nodes[$nodeKey]['']['keyword'] = $keyWord;
                            }
                        }
                    }
                } else {
                    if(!in_array($val,array_keys($nodes[$nodeKey]))) {      //新增字符
                        if($key == $lastKey) {      //此處為結(jié)束字符 創(chuàng)建結(jié)束標識&關(guān)鍵詞結(jié)束  兼容字符截斷  例如: 積分少了 積分
                            $nodes[$nodeKey][$val]['is_end'] = 1;
                            $nodes[$nodeKey][$val]['level'] = $key;
                            $nodes[$nodeKey][$val]['keyword'] = $keyWord;
                        } else {        //創(chuàng)建下一個節(jié)點 回寫下個節(jié)點的指針標識
                            $newKey = count($nodes);        //下一個字段指針
                            $nodes[$newKey] = array();
                            $nodes[$nodeKey][$val]['next_pointer'] = $newKey;
                            $nodes[$nodeKey][$val]['level'] = $key;
                            $nodeKey = $newKey;         //指針后移
                        }
                    } else {        //字符存在
                        $nodeInfo = $nodes[$nodeKey][$val];
                        if($key == $lastKey) {      //字符被終結(jié)  創(chuàng)建結(jié)束標識&關(guān)鍵詞結(jié)束
                            $nodes[$nodeKey][$val]['is_end'] = 1;
                            $nodes[$nodeKey][$val]['keyword'] = $keyWord;
                        } else {    //字符未被終結(jié)
                            if($nodeInfo['next_pointer']) { //指針后移
                                $nodeKey = $nodeInfo['next_pointer'];
                            } else {        //創(chuàng)建后續(xù)指針
                                $newKey = count($nodes);
                                $nodes[$newKey] = array();
                                $nodes[$nodeKey][$val]['next_pointer'] = $newKey;
                                $nodeKey = $newKey;
                            }
                        }
                    }

                }
            }
        }
        //todo 節(jié)點列表寫入存儲  redis mysql都可以
        $this->nodes = $nodes;

//        $this->_initialize();
    }

    /**
     * 清空敏感詞
     * */
    public function clearkeywors() {
        $nodes = array(array());
        //todo 節(jié)點列表寫入存儲  redis mysql都可以
        $this->nodes = $nodes;
    }

    /**
     * 獲取敏感詞
     * @return  array       關(guān)鍵詞節(jié)點
     * */
    protected function getKeywords() {
        //todo 節(jié)點列表獲取  redis mysql都可以
        $nodes = array();
        if(!$nodes) {
            $nodes = array(array());
        }
        return $nodes;
    }


    /**
     * 將字符串轉(zhuǎn)換成字符數(shù)組
     * @param   string      字符串
     * @return  array
    * */
    protected function get_chars($utf8_str){
        $s = $utf8_str;
        $len = strlen($s);
        if($len == 0) return array();
        $chars = array();
        for($i = 0;$i < $len;$i++){
            $c = $s[$i];
            $n = ord($c);
            if(($n >> 7) == 0){       //0xxx xxxx, asci, single
                $chars[] = $c;
            }
            else if(($n >> 4) == 15){     //1111 xxxx, first in four char
                if($i < $len - 3){
                    $chars[] = $c.$s[$i + 1].$s[$i + 2].$s[$i + 3];
                    $i += 3;
                }
            }
            else if(($n >> 5) == 7){  //111x xxxx, first in three char
                if($i < $len - 2){
                    $chars[] = $c.$s[$i + 1].$s[$i + 2];
                    $i += 2;
                }
            }
            else if(($n >> 6) == 3){  //11xx xxxx, first in two char
                if($i < $len - 1){
                    $chars[] = $c.$s[$i + 1];
                    $i++;
                }
            }
        }
        return $chars;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容