Trie字典树在工作中的应用

项目中有时需要对文案过滤屏蔽词,违禁网址等,或者在审核中展示高亮词,涉及到对关键字的查找。

在这里插入图片描述
这里介绍字典树的实现方法。

字典树是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串,所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

一 树的形状

举例说明:“女性”、“女性服务”、“女性服务员特殊” 是屏蔽词,“女”、“女性服务员” 是相近的合法词。
生成如下字典树,END=1标识屏蔽词,END=3标识白名单,查询时从根往下遍历。

array (
  '女' => 
  array (
    'END' => 3,
    '性' => 
    array (
      'END' => 1,
      '服' => 
      array (
        '务' => 
        array (
          'END' => 1,
          '员' => 
          array (
            'END' => 3,
            '特' => 
            array (
              '殊' => 
              array (
                'END' => 1,
              ),
            ),
          ),
        ),
      ),
    ),
  ),
);

二 树的生成

/**
 * @brief   Trie字典树
 * @note    生成
 * @author  jichenghan
 * @version 1.0
 * @since   2022-08-30
 */
class MakeTrie
{
	//字典树
    private $aTrie;
       
    /* 从字典中读取词汇,初始化字典树文件 */
    public function initTrieDDFile()
    {
        include getenv("INCPATH")."/co/dd/dd_prohibitedword.php";//1.屏蔽词
        foreach ($dd_prohibitedword as $sCValue)
        {
            $sCValue = mb_strtolower($sCValue, "GBK");
            $this->addToTrieDD($sCValue, 1);
        }
        include getenv("INCPATH")."/co/dd/dd_prohibitedword_whitelist.php";//3.白名单
        foreach ($dd_prohibitedword_whitelist as $sCValue)
        {
            $sCValue = mb_strtolower($sCValue, "GBK");
            $this->addToTrieDD($sCValue, 3);
        }
        if (! $this->writeTrieDDFile("array"))
        {
            return false;
        }
        return true;
    }

    /* 将关键字加到字典树中 */
    private function addToTrieDD($p_sCValue, $p_sType, $sEncoding = 'GBK')
    {
        $iLength = mb_strlen($p_sCValue, $sEncoding);
        $sWord   = mb_substr($p_sCValue, 0, 1, $sEncoding);
        //首字初始化
        if (!isset($this->aTrie[$sWord]))
        {
            $this->aTrie[$sWord] = array();
        }
		//递归调用
        if ($iLength > 1)
        {
            $p_sCValue = mb_substr($p_sCValue, 1, $iLength-1, $sEncoding);
            $this->addToTrieDD($p_sCValue, $this->aTrie[$sWord], $p_sType, $sEncoding);
        }
        //结束标记
        else
        {
            $this->aTrie[$sWord]['END'] = $p_sType;
        }
    }

    /* 生成字典树文件 */
    private function writeTrieDDFile($p_stype="array")
    {
        if ($p_stype == 'json')
        {
            $file = getenv("BASEPATH")."/in/dd/newprohibitedword.json";
            $text = json_encode($this->aTrie);
        }
        else
        {
            $file = getenv("INCPATH")."/co/dd/dd_newprohibitedword.php";

            $text = var_export($this->aTrie, true);
            $text = '<?php return '.$text.';';
        }
        if (file_put_contents($file, $text) === false)
        {
            return false;
        }
        
        return true;
    }
}

三 树的遍历

/**
 * @brief   Trie字典树
 * @note    使用
 * @author  jichenghan
 * @version 1.0
 * @since   2022-08-30
 */
class UseTrie
{
    public $aNewProhibitedWord;//屏蔽词
    public $aWordOffset;//屏蔽词在文本中的位置偏移 array(偏移量,长度)

    public function __construct()
    {
        //上面生成的字典树
        $this->aNewProhibitedWord = getDict('dd_newprohibitedword');
    }

    public function checkProhibitedWord($p_sText)
    {
        $aResult = ['status' => 0, 'rowcount' => 0, 'errormsg'=>''];
        if (empty($this->aNewProhibitedWord) || !is_array($this->aNewProhibitedWord))
        {
            $aResult['errormsg'] = '字典文件异常';
            return $aResult;
        }

        $p_sText = mb_strtolower($p_sText, "GBK"); //转小写
        $iLength = mb_strlen($p_sText, 'GBK');

        $iFlag = false;
        $this->aWordOffset = [];
        for ($i = 0; $i < $iLength; $i++)
        {
            $this->checkTrieProhibitedWord($p_sText, $i, $iLength);
        }

        if (!empty($this->aWordOffset))
        {
            $aResult['status'] = 1;
            $aResult['rowcount'] = count($this->aWordOffset);
        }
        else
        {
            $aResult['status'] = 2;
        }

        $aResult['wordoffset'] = $this->aWordOffset;
        return $aResult;
    }

    /* 检查替换屏蔽词 */
    private function checkTrieProhibitedWord($p_sText, &$iBeginIndex, &$iLength)
    {
        $aResult = $this->searchTrieProhibitedWord($p_sText, $iBeginIndex, $iLength);
        if ($aResult === false)
        {
            return false;
        }

        $aWhiteWordLength   = $aResult['whitewordlength'];
        $aReplaceWord       = $aResult['replaceword'];
        $aReplaceWordLength = $aResult['replacewordlength'];
        if (!empty($aWhiteWordLength))
        {
            /* 找到白名单,向前移动白名单的长度 */
            $iCount = count($aWhiteWordLength);
            $iWhiteWordLength = $aWhiteWordLength[$iCount-1];
            /* 屏蔽词为空 或者 白名单的长度大于屏蔽词的长度 */
            if (empty($aReplaceWordLength) || $iWhiteWordLength > $aReplaceWordLength[count($aReplaceWordLength)-1])
            {
                if ($iWhiteWordLength >= 1)
                {
                    $iBeginIndex += $iWhiteWordLength-1;
                }
                return false;
            }
        }
        $iCount = count($aReplaceWord);
        /* 屏蔽词,从后往前遍历 */
        for ($i=$iCount-1; $i >= 0; $i--)
        {
            $iProhibitedWordLength = $aReplaceWordLength[$i];
            
            //修改下次循环起始位置
            $this->aWordOffset[] = array($iBeginIndex, $iProhibitedWordLength);
            $iBeginIndex += $iProhibitedWordLength-1;
            return true;
        }

        return false;
    }

    private function searchTrieProhibitedWord($p_sText, $iBeginIndex, $iLength)
    {
        $sProhibitedWord = '';
        $iWordLength = 0;
        $aReplaceWord = array();
        $aReplaceWordLength = array();
        $aWhiteWordLength = array();
        $iFlag = false;
        $aNewProhibitedWord = $this->aNewProhibitedWord;

        /* 从$iBeginIndex开始,查找是否有匹配的屏蔽词 */
        for ($i = $iBeginIndex; $i < $iLength; $i++)
        {
            $sWord = mb_substr($p_sText, $i, 1, 'GBK'); //检验单个字
            $sWord = mb_strtolower($sWord, "GBK"); //转小写
            //如果树中不存在,结束
            if (!isset($aNewProhibitedWord[$sWord]))
            {
                break;
            }
            //如果存在
            $sProhibitedWord .= $sWord;
            $iWordLength++;
            $aNewProhibitedWord = $aNewProhibitedWord[$sWord];
            if (isset($aNewProhibitedWord['END']))
            {
                /* 3-白名单 结束处理 */
                if ($aNewProhibitedWord['END'] === 3)
                {
                    $aWhiteWordLength[] = $iWordLength;
                }
                /* 1-屏蔽词 */
                elseif ($aNewProhibitedWord['END'] === 1)
                {
                    $aReplaceWord[] = $sProhibitedWord;
                    $aReplaceWordLength[] = $iWordLength;
                }
                $iFlag = true;
            }
        }

        if ($iFlag == true)
        {
            $a = array(
                'replaceword'       => $aReplaceWord,
                'replacewordlength' => $aReplaceWordLength,
                'whitewordlength'   => $aWhiteWordLength,
            );
            return $a;
        }

        return false;
    }
}

三 使用方式

录入如下文字:

   $aTrie = new UseTrie();
   $aTrie->checkProhibitedWord('招聘女性服务员和女性服务员特殊,提供女性服务');

第一轮从“女”字开头遍历“女性服务员”得到的偏移量:

array ( 
	'replaceword' => array ( 0 => '女性', 1 => '女性服务', ), 
	'replacewordlength' => array ( 0 => 2, 1 => 4, ), 
	'whitewordlength' => array ( 0 => 1, 1 => 5, ), 
)

找到“女性”和“女性服务”两个屏蔽词,对应的长度是2和4,找到“女”和“女性服务员”两个白名单,对应的长度是1和5,因为白名单的最大长度5大于屏蔽词的最大长度4,所以“女性服务员”不算屏蔽词。

整体返回结果:

array ( 
	'wordoffset' => array ( 
		0 => array ( 0 => 8, 1 => 7, ), 
		1 => array ( 0 => 18, 1 => 4, ), 
	), 
)

对应从下标为8开始长度为7的“女性服务员特殊”,和下标为18开始长度为4的“女性服务”。
这样就找到在一段文本中的屏蔽词,并且过滤了白名单。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值