• 技术文章 >后端开发 >php教程

    PHP实现非法词汇过滤(算法分析)

    藏色散人藏色散人2022-10-11 16:32:41转载1796

    算法简介

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

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

    实现难点

    构造一棵树简单,关键点是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入门到就业线上直播课:查看学习

    以上就是PHP实现非法词汇过滤(算法分析)的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:learnku,如有侵犯,请联系admin@php.cn删除

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    自己动手写 PHP MVC 框架:点击学习

    快速了解MVC架构、了解框架底层运行原理

    专题推荐:php
    上一篇:cgi、fast-cgi、php-fpm的关系(附流程图) 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• php怎么过滤掉看不见的特殊字符串• php怎么过滤字符串只获取数字• 三分钟带你搞定PHP过滤器(实例详解)• php能不能过滤字符串的空格
    1/1

    PHP中文网