敏感词过滤(DFA算法)PHP实现(解决包含词匹配不到的问题和算法优化)

首先这篇文章的代码是根据:敏感词过滤(DFA算法)PHP实现做了重构和优化处理,解决了部分包含词语无法匹配到和算法效率优化,关键词多的情况或者搜索的字符较大的情况优化,感谢SmallYard分享的代码支持。

先说问题:

1 之前版本存在包含词无法匹配问题:比如 银行  银行监控  其中只能匹配银行监控没办法匹配银行

2 在查询字符较多的时候速度会有些慢。

 

解决方案:

1 调整Tree结构增加end字段,对已知关键词单个节点上增加end字段,以来判断关键词匹配是否匹配到了,例:银行 匹配到行字代表匹配成功一个关键词,但匹配还继续下一个匹配直到结束,也就是一次匹配可以匹配多个相近的词。

2 在循环的时候减少循环次数,对已知查询到的关键词做标记,下次循环跳过已知关键词。例子:我要办理银行监控业务,整体来看要循环10次,子循环里面再循环10次(极端情况),如果查询到银行监控这个关键词,银字在字符串第4位,银行监控长度为4位,下次开始循环的位数就应该为8,跳过567三个循环。

/**
 * Description of DfaCheck
 *
 *
 * @author Faith<303529990@qq.com>
 * @date   2019-8-2 18:15:40 (修改)
 */
class DfaCheck {

    private static $instance = null;

    /**
     * 替换符号
     * @var string
     */
    private static $replaceSymbol = "*";

    /**
     * 敏感词树
     * @var array
     */
    private static $sensitiveWordTree = [];

    /**
     * 获取实例
     */
    public static function getInstance() {
        if (!(self::$instance instanceof DfaCheck)) {
            return self::$instance = new self;
        }
        return self::$instance;
    }

    /**
     * 添加敏感词,组成树结构。
     * 例如敏感词为:傻子是傻帽,白痴,傻蛋 这组词组成如下结构。
     * [
     *     [傻] => [
     *           [子]=>[
     *               [是]=>[
     *                  [傻]=>[
     *                      [帽]=>[false]
     *                  ]
     *              ]
     *          ],
     *          [蛋]=>[false]
     *      ],
     *      [白]=>[
     *          [痴]=>[false]
     *      ]
     *  ]
     * @param $file_path 敏感词库文件路径
     */
    public static function addSensitiveWords(string $file_path) {
        $text = self::readFile($file_path);
        foreach ($text as $key => $words) {
            $len = mb_strlen($words);
            $treeArr = &self::$sensitiveWordTree;
            for ($i = 0; $i < $len; $i++) {
                $word = mb_substr($words, $i, 1);
                //敏感词树结尾记录状态为false;
                if ($i + 1 == $len) {
                    $treeArr[$word]['end'] = false;
                }
                $treeArr = &$treeArr[$word] ?? false;
            }
        }
    }

    /**
     * 执行过滤
     * @param string $txt
     * @return string
     */
    public static function execFilter(string $txt): string {
        $wordList = self::searchWords($txt);
        if (empty($wordList))
            return $txt;
        return strtr($txt, $wordList);
    }

    /**
     * 搜索敏感词
     * @param string $txt
     * @return array
     */
    public static function searchWords(string $txt): array {
        $txtLength = mb_strlen($txt);
        $wordList = [];
        for ($i = 0; $i < $txtLength; $i++) {
            //检查字符是否存在敏感词树内,传入检查文本、搜索开始位置、文本长度
            $lenList = self::checkWordTree($txt, $i, $txtLength);
            foreach ($lenList as $key => $len) {
                if ($len > 0) {
                    //搜索出来的敏感词
                    $word = mb_substr($txt, $i, $len);
                    $wordList[$word] = str_repeat(self::$replaceSymbol, $len);   //存在敏感词,进行字符替换。
                    if (($key + 1) == count($lenList)) {
                        $i += $len;
                    }
                }
            }
        }
        return $wordList;
    }

    /**
     * 检查敏感词树是否合法
     * @param string $txt 检查文本
     * @param int $index 搜索文本位置索引
     * @param int $txtLength 文本长度
     * @return int 返回不合法字符个数
     */
    public static function checkWordTree(string $txt, int $index, int $txtLength): array {
        $treeArr = &self::$sensitiveWordTree;
        $wordLength = 0; //敏感字符个数
        $wordLengthArray = [];
        $flag = false;
        for ($i = $index; $i < $txtLength; $i++) {
            $txtWord = mb_substr($txt, $i, 1); //截取需要检测的文本,和词库进行比对
            //如果搜索字不存在词库中直接停止循环。
            if (!isset($treeArr[$txtWord])) {
                break;
            }
            $wordLength++;
            if (isset($treeArr[$txtWord]['end'])) {//检测还未到底
                $flag = true;
                $wordLengthArray[] = $wordLength;
            }
            $treeArr = &$treeArr[$txtWord]; //继续搜索下一层tree
        }
        //没有检测到敏感词,初始化字符长度
        $flag ?: $wordLength = 0;
        return $wordLengthArray;
    }

    /**
     * 读取文件内容
     * @param string $file_path
     * @return Generator
     */
    private static function readFile(string $file_path): Generator {
        $handle = fopen($file_path, 'r');
        while (!feof($handle)) {
            yield trim(fgets($handle));
        }
        fclose($handle);
    }

    private function __clone() {
        throw new \Exception("clone instance failed!");
    }

    private function __wakeup() {
        throw new \Exception("unserialize instance failed!");
    }

}

function getMillisecond() {
    list($microsecond, $time) = explode(' ', microtime()); //' '中间是一个空格
    return (float) sprintf('%.0f', (floatval($microsecond) + floatval($time)) * 1000);
}

$dfa = new DfaCheck();
$dfa->addSensitiveWords('test.txt');

$time = getMillisecond();
$times = time();
echo "\n";

$txt = "苹果短信广告推送设备那里买〔必看介绍〕";
echo $dfa->execFilter($txt);
echo "\n";
var_dump($dfa->searchWords($txt));
echo getMillisecond() - $time, "\n";
echo time() - $times, "\n";
echo mb_strlen($txt);

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值