PHP實現非法詞彙過濾(演演算法分析)

2022-10-11 18:01:04

演演算法簡介

將關鍵詞構造成一顆樹,每個字都是一個節點。

遍歷需要過濾的語句,將語句的每個字都去樹中查詢,看看是否存在。

實現難點

構造一棵樹簡單,關鍵點是php中遍歷字串需要自己正確的得到單個字元的長度。
簡單遍歷字串的方法如下:

$strLen = mb_strlen($str);
for ($i = 0; $i < $strLen; $i++) {
    echo mb_substr($str, $i, 1, "utf8"),PHP_EOL;
}
登入後複製

該方法是利用mb_*系列函數來正確擷取每個字元,處理大量字串時速度非常慢,我猜測是:mb_substr每擷取一個字元,都要計算該字串之前,有多少個字元。
正確的遍歷字串的方式是按utf8的編碼規律來擷取字串,具體請看下文。

演演算法實現

<?php
/**
 * 非法關鍵詞檢查
 */
class SensitiveWords
{
    protected $tree = null;
    protected $callIsNumeric = true;
    /**
     * 非法詞彙列表,一個非法詞彙佔用一行
     */
    public function __construct($path = __DIR__ . '/sensitiveWords.txt')
    {
        $this->tree = new WordNode();
        $file = fopen($path, "r");
        while (!feof($file)) {
            $words = trim(fgets($file));
            if ($words == '') {
                continue;
            }
            //存在純數位的非法詞彙
            if (is_numeric($words)) {
                $this->callIsNumeric = false;
            }
            $this->setTree($words);
        }
        fclose($file);
    }

    protected function setTree($words)
    {
        $array = $this->strToArr($words);
        $tree = $this->tree;
        $l = count($array) - 1;
        foreach ($array as $k => $item) {
            $tree = $tree->getChildAlways($item);
            if ($l == $k) {
                $tree->end = true;
            }
        }
    }

    /**
     * 返回包含的非法詞彙
     * @param string $str
     * @return array
     */
    public function check($str)
    {
        //先壓縮字串
        $str = trim(str_replace([' ', "\n", "\r"], ['', '', ''], $str));
        $ret = [];
        loop:
        $strLen = strlen($str);
        if ($strLen === 0) {
            return array_unique($ret);
        }
        //非法詞彙中沒有純數位的非法詞彙,待檢測字串又是純數位的,則跳過不再檢查
        if ($this->callIsNumeric && is_numeric($str)) {
            return array_unique($ret);
        }
        //挨個字元進行判斷
        $tree = $this->tree;
        $words = '';
        for ($i = 0; $i < $strLen; $i++) {
            //unicode範圍 --> ord 範圍
            //一位元組 0-127 --> 0 - 127
            //二位元組 128-2047 --> 194 - 223
            //三位元組 2048-65535 --> 224 - 239
            //四位元組 65536-1114111 --> 240 - 244
            //@see http://shouce.jb51.net/gopl-zh/ch3/ch3-05.html
            $ord = ord($str[$i]);
            if ($ord <= 127) {
                $word = $str[$i];
            } elseif ($ord <= 223) {
                $word = $str[$i] . $str[$i + 1];
                $i += 1;
            } elseif ($ord <= 239) {
                $word = $str[$i] . $str[$i + 1] . $str[$i + 2];
                $i += 2;
            } elseif ($ord <= 244) {
                //四位元組
                $word = $str[$i] . $str[$i + 1] . $str[$i + 2] . $str[$i + 3];
                $i += 3;
            } else {
                //五位元組php都溢位了
                //Parse error: Invalid UTF-8 codepoint escape sequence: Codepoint too large
                continue;
            }
            //判斷當前字元
            $tree = $tree->getChild($word);
            if (is_null($tree)) {
                //當前字不存在,則擷取後再次迴圈
                $str = substr($str, $i + 1);
                goto loop;
            } else {
                $words .= $word;
                if ($tree->end) {
                    $ret[] = $words;
                }
            }
        }
        return array_unique($ret);
    }

    protected function strToArr($str)
    {
        $array = [];
        $strLen = mb_strlen($str);
        for ($i = 0; $i < $strLen; $i++) {
            $array[] = mb_substr($str, $i, 1, "utf8");
        }
        return $array;
    }
}
/**
 * 單個字元的節點
 */
class WordNode
{
    //是否為非法詞彙末級節點
    public $end = false;
    //子節點
    protected $child = [];

    /**
     * @param string $word
     * @return WordNode
     */
    public function getChildAlways($word)
    {
        if (!isset($this->child[$word])) {
            $this->child[$word] = new self();
        }
        return $this->child[$word];
    }

    /**
     * @param string $word
     * @return WordNode|null
     */
    public function getChild($word)
    {
        if ($word === '') {
            return null;
        }
        if (isset($this->child[$word])) {
            return $this->child[$word];
        }
        return null;
    }
}
登入後複製

推薦學習:《》

php入門到就業線上直播課:

以上就是PHP實現非法詞彙過濾(演演算法分析)的詳細內容,更多請關注TW511.COM其它相關文章!