首页 » 收集 » 正文内容
PHP实现非法词汇过滤(算法分析)
寻梦xunm| 331| 收集
1年前
超过654天 温馨提示
本文最后更新于2022年12月06日,已超过654天没有更新,若内容或图片失效,请留言反馈。

算法简介
将关键词构造成一颗树,每个字都是一个节点。

遍历需要过滤的语句,将语句的每个字都去树中查找,看看是否存在。

实现难点
构造一棵树简单,关键点是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;

    }

}
0 赞 or 打赏
喜欢就打赏一点
微信 支付宝
隐私
Q Q:1340326824
邮箱:vipshiyi@qq.com
QQ群:422720328

我的音乐

微博客-专为自己编写开发的源码