使用前缀树实现敏感词过滤

在一些论坛或者博客类的项目需要对内容进行敏感词的匹配以及脱敏操作,像这类的功能就可以使用前缀树实现,接下来我们就使用哈希去实现前缀树。(gin框架的路由树也是基于前缀树实现的)

什么是前缀树?

前缀树(Prefix Tree),也被称为字典树(Trie),是一种用于高效存储和检索字符串的数据结构。它的主要特点是能够快速地查找具有相同前缀的字符串集合。

既然使用前缀树做字符匹配那么它有什么特点?

  1. 高效的字符串存储:前缀树使用共享前缀的方式存储字符串,因此可以高效地存储大量字符串集合。相同的前缀会被共享,减少了存储空间的需求。
  2. 快速的字符串搜索:前缀树可以快速地搜索具有相同前缀的字符串集合。通过从根节点开始,按照字符依次向下遍历树的路径,可以高效地定位到匹配的字符串。
  3. 前缀匹配:前缀树可以快速地找出与给定字符串具有相同前缀的所有字符串。通过遍历匹配到的子树,可以得到所有以给定前缀开头的字符串。
  4. 支持动态更新:前缀树可以动态地插入和删除字符串。当需要插入或删除一个字符串时,只需按照字符依次遍历树的路径,根据需要插入或删除节点。
  5. 适用于字符串相关操作:前缀树广泛应用于字符串相关的操作,如自动补全、拼写检查、词频统计、字符串搜索和匹配等。由于其高效的存储和检索特性,可以在这些场景下提供快速和高效的操作。
  6. 路径压缩:前缀树具有路径压缩的能力,可以减少不必要的节点,节省存储空间。当某个节点只有一个子节点时,可以将中间节点合并,减少冗余的节点。

实现思路

假设我们有以下敏感词列表:["bad", "evil", "danger"]

		(root)                    ["bad", "evil", "danger"]/ | \b  e  d/   |   \a    v    a/     |     \d      i      n|       \l        g|         \e          e

在这个前缀树中,每个节点代表一个字符,根节点表示空字符,每个节点的子节点表示对应的字符。叶子节点表示一个完整的敏感词。

遍历实现思路:

  1. 遍历待检测的文本。
  2. 从根节点开始,按照文本中的字符依次遍历前缀树的路径。
  3. 如果在遍历过程中遇到空路径,或者遍历完成后当前节点不是叶子节点,则表示没有匹配到敏感词,继续遍历下一个字符。
  4. 如果遍历完成后当前节点是叶子节点,则表示匹配到了敏感词,根据需求进行相应的处理,如替换为星号或进行其他处理。

代码实现

package sensitiveWordimport ("io/ioutil""os""project/consts""project/types""strings""go.uber.org/zap"
)// SensitiveMap 使用前缀树实现敏感词过滤
type SensitiveMap struct {sensitiveNode map[string]interface{}isEnd         bool
}// Target 目标词索引、长度
type Target struct {Indexes []intLen     int
}var s *SensitiveMap// getMap 将自己的词库放入/static/dictionary下,放入下列切片中!!!!
func getMap() *SensitiveMap {if s == nil {var Sen []stringSen = append(Sen, consts.OtherSen)s = InitDictionary(s, Sen)}return s
}// CheckSensitiveWord 判断是否有敏感词
func CheckSensitiveWord(content string) []*types.SensitiveWord {var res []*types.SensitiveWordsensitiveMap := getMap()target := sensitiveMap.FindAllSensitive(content)for k, v := range target {t := &types.SensitiveWord{Word:    k,Indexes: v.Indexes,Length:  v.Len,}res = append(res, t)}return res
}// FindAllSensitive 查找所有的敏感词
func (s *SensitiveMap) FindAllSensitive(text string) map[string]*Target {content := []rune(text)contentLength := len(content)result := falseta := make(map[string]*Target)for index := range content {sMapTmp := starget := ""in := indexresult = falsefor {wo := string(content[in])target += woif _, ok := sMapTmp.sensitiveNode[wo]; ok {if sMapTmp.sensitiveNode[wo].(*SensitiveMap).isEnd {result = truebreak}if in == contentLength-1 {break}sMapTmp = sMapTmp.sensitiveNode[wo].(*SensitiveMap)in++} else {break}}if result {if _, targetInTa := ta[target]; targetInTa {ta[target].Indexes = append(ta[target].Indexes, index)} else {ta[target] = &Target{Indexes: []int{index},Len:     len([]rune(target)),}}}}return ta
}// InitDictionary 初始化字典,构造前缀树
func InitDictionary(s *SensitiveMap, dictionary []string) *SensitiveMap {// 初始化字典树s = initSensitiveMap()var dictionaryContent []stringfor i := 0; i < len(dictionary); i++ {dictionaryContentTmp := ReadDictionary(dictionary[i])// TODO:将所有词拿到dictionaryContent = append(dictionaryContent, dictionaryContentTmp...)}for _, words := range dictionaryContent {sMapTmp := s// 将每一个词转换为一个rune数组,不光英文、中文w := []rune(words)wordsLen := len(w)for i := 0; i < wordsLen; i++ {t := string(w[i])isEnd := falseif i == (wordsLen - 1) {isEnd = true}func(tx string) {// 查看当前字符在不在树结构,在的话就复用节点if _, ok := sMapTmp.sensitiveNode[tx]; !ok {sMapTemp := new(SensitiveMap)sMapTemp.sensitiveNode = make(map[string]interface{})sMapTemp.isEnd = isEndsMapTmp.sensitiveNode[tx] = sMapTemp}sMapTmp = sMapTmp.sensitiveNode[tx].(*SensitiveMap)sMapTmp.isEnd = isEnd}(t)}}return s
}// initSensitiveMap 初始化map
func initSensitiveMap() *SensitiveMap {return &SensitiveMap{sensitiveNode: make(map[string]interface{}),isEnd:         false,}
}// ReadDictionary 将词库读取出来
func ReadDictionary(path string) []string {file, err := os.Open(path)if err != nil {zap.L().Error("read dictionary file failed:", zap.Error(err))return nil}defer file.Close()str, err := ioutil.ReadAll(file)dictionary := strings.Fields(string(str))return dictionary
}

上述是前缀树实现的基本思路,使用map构造前缀树目的就是为了快速定位节点。

优化思路

我们判断的时候肯定不可以每次读区词库,这样io消耗过大。初步思路就是可以把前缀树加载到缓存中去,到时候操作缓存匹配敏感词效率就高了,如果要更新词库就重新加载缓存即可,更新词库树形结构变化不会过大,即可动态更新。

算法优化思路(DFA算法优化)

如果大规模过滤敏感词的时候,前缀树就会遇到性能瓶颈,这个时候就需要使用dfa进行前缀树的算法优化。

步骤如下

当应用DFA算法对前缀树进行优化时,需要将前缀树转换成DFA状态机。下面是一种常见的转换步骤:

  1. 构建初始状态:DFA状态机的初始状态对应于前缀树的根节点。
  2. 逐层构建状态转移:从初始状态开始,按照前缀树的层次结构逐层构建状态转移。对于每个状态,根据前缀树节点的子节点和字符的对应关系,确定该状态的转移条件。
  3. 标记终止状态:对于前缀树中表示一个完整字符串的节点(叶子节点),标记相应的状态为终止状态。
  4. 合并等价状态:在构建状态转移时,可能会出现等价的状态。通过状态合并操作,将等价状态合并为一个状态,从而减少状态的数量。
  5. 优化状态转移表:根据具体情况,可以对状态转移表进行优化,如压缩存储、使用哈希表等。

这里就不展开讲了

如果有更好的思路,欢迎讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/34382.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript之BOM+window对象+定时器+location,navigator,history对象

一.BOM概述 BOM即浏览器对象模型,它提供了独立于内容而与窗口进行交互的对象 BOM的顶级对象是window 二.window对象的常见事件 1.窗口加载事件window.onload window.onload function(){} 或者 window.addEventListener("onload" , function(){}); window.onlo…

Mybatis-plus动态条件查询QueryWrapper的使用

Mybatis-plus动态条件查询QueryWrapper的使用 一&#xff1a;queryWrapper介绍 queryWrapper是mybatis plus中实现查询的对象封装操作类&#xff0c;可以封装sql对象&#xff0c;包括where条件&#xff0c;order by排序&#xff0c;select哪些字段等等&#xff0c;他的层级关…

CentOS7连接网络

1.下载centos7镜像文件 2.安装centos7 3.修改网卡,ens33. 注意: 这里使用的是dhcp,设置IPADDR192.168.31.64一方面是为了后面使用crt或者MobaXterm连接,另一方面它和windows电脑的网卡要一致.这样才可以连接到网络.win r,输入cmd,打开命令窗口输入ipconfig.可以看到IPv4: 102…

phpstorm配置ftp同步文件到服务器

这里的默认快捷键 不是 CtrlS &#xff1b;需要设置快捷键&#xff0c;这里原来是save all操作时上传文件到服务器&#xff1b; ** 设置好快捷键后按 CtrlS就会同步文件&#xff08;添加删除文件后保存&#xff0c;服务器也会同步&#xff09; ** 搜索出save all 后&#xf…

NPM与外部服务的集成(上)

目录 1、关于访问令牌 1.1 关于传统令牌 1.2 关于粒度访问令牌 2、创建和查看访问令牌 2.1 创建访问令牌 在网站上创建传统令牌 在网站上创建粒度访问令牌 使用CLI创建令牌 CIDR限制令牌错误 查看访问令牌 在网站上查看令牌 在CLI上查看令牌 令牌属性 1、关于访问令…

报错注入(主键重复)攻击原理

基本原理 利用数据表中主键不能重复的特点&#xff0c;通过构造重复的主键&#xff0c;使得数据库报错&#xff0c;并将报错结果返回到前端。 SQL说明函数 以pet数据表为例进行说明 rond(): 返回[0,1)区间内的任意浮点数。 count(): 返回每个组的列行数。 如&#xff0…

SWIG使用方法

安装 下载 swigwin软件包&#xff0c;解压到合适的位置&#xff0c;然后将路径添加到环境变量即可。 编写C代码 //vector.hpp class Vector { private:int x;int y; public:Vector(int,int);double abs();void display(); };//vector.cpp #include "vector.hpp" …

CI/CD—K8S 基本理解与部署

1 K8S 是什么 Kubernetes 是一款容器的编排调度工具&#xff0c;来源于 Google 开源的 Brog 系统。Kubernetes简称K8S&#xff0c;是用8代替8个字符 “ubernete” 而成的缩写&#xff0c;用于管理云平台中多个主机上的容器化的应用&#xff0c;Kubernetes 的目标是让部署容器化…

解决createRoot is not a function

报错&#xff1a; 出现的原因&#xff1a;在于把react18使用的vite构建&#xff0c;在开发中因react版本太高与其他库不兼容&#xff0c;而在降级的时候&#xff0c;出现以上dom渲染出现报错。 解决&#xff1a;将 src/index.j文件改成如下 import React from react; import…

【数据结构与算法】十大经典排序算法-冒泡排序

&#x1f31f;个人博客&#xff1a;www.hellocode.top &#x1f3f0;Java知识导航&#xff1a;Java-Navigate &#x1f525;CSDN&#xff1a;HelloCode. &#x1f334;掘金&#xff1a;HelloCode &#x1f31e;知乎&#xff1a;HelloCode ⚡如有问题&#xff0c;欢迎指正&#…

form 表单恢复初始数据

form 表单恢复初始数据 在现代的 Web 开发中&#xff0c;表单是不可或缺的组件之一。用户可以通过表单输入和提交数据&#xff0c;而开发者则需要对这些数据进行处理和存储。然而&#xff0c;在某些情况下&#xff0c;我们可能需要重置表单并恢复到最初的状态。 本文介绍了如…

MATLAB计算连续月份的不同栅格数据间的相关系数(输出为tif影像)

%先导入投影信息&#xff0c;某个影像的路径就行&#xff08;最好是你分析的数据中的一个&#xff09; [a,R]readgeoraster(G:\SIF\Global-AI_monthly_v3\bi\199001.tif); infogeotiffinfo(G:\SIF\Global-AI_monthly_v3\bi\199001.tif); [m,n]size(a); i1;gwzeros(m*n,24); %此…

锐捷设备密码管理、密码恢复、恢复出厂设置

目录 配置登录用户名密码以及Enable密码 只需要密码登录 需要用户名和密码登录&#xff08;无AAA&#xff09; 需要用户名和密码登录&#xff08;有AAA&#xff09; 密码恢复 Web密码忘记 Telnet/SSH密码忘记 Console密码忘记 所有密码都忘记&#xff0c;通过Console进…

服务器数据恢复-RAID5上层Hyper-V虚拟机数据恢复案例

服务器数据恢复环境&#xff1a; 一台Windows Server服务器&#xff0c;部署Hyper-V虚拟化环境&#xff0c;虚拟机的硬盘文件和配置文件存放在一台DELL存储中。该存储中有一组由4块硬盘组建的RAID5阵列&#xff0c;用来存放虚拟机的数据文件&#xff0c;另外还有一块大容量硬盘…

nvm的使用

nvm (Node Version Manager) 是管理Node.js版本的一个工具,主要的使用方法如下: 安装nvm: https://github.com/coreybutler/nvm-windows/releases或者通过wget、git等下载安装脚本,然后运行安装。 验证安装: command -v nvm列出可安装的版本: nvm ls-remote安装指定版本: …

【TypeScript】进阶之路语法细节,类型和函数

进阶之路 类型别名(type)的使用接口(interface)的声明的使用二者区别&#xff1a; 联合类型和交叉类型联合类型交叉类型 类型断言获取DOM元素 非空类型断言字面量类型的使用类型缩小&#xff08;类型收窄&#xff09;TypeScript 函数类型函数类型表达式内部规则检测函数的调用签…

keil下载程序具体过程:概述

一、前言 keil下载程序具体过程将由一系列的博客组成&#xff0c;将深入探讨keil这种IDE下载镜像文件时具体做了哪些事情。我们平常下载镜像的时候&#xff0c;只是点击了一下Download按钮&#xff0c;剩下的都由keil替代我们完成了。本系列博客将揭示这一过程&#xff0c;keil…

【云原生】kubernetes控制器deployment的使用

目录 ​编辑 1 Controller 控制器 1.1 什么是 Controller 1.2 常见的 Controller 控制器 1.3 Controller 如何管理 Pod 2 Deployment 2.1 创建 deployment 2.2 查看 deployment 2.3 扩缩 deployment 2.4 回滚 deployment 2.5 删除 deployment 1 Controller 控制器 …

markdown命令模板

markdown快速入门(typora) 1、代码块 //代码块语 public static void main(String[] args){}//linux下spring项目的启动命令 # java -jar blog start ## 2、标题&#xff1a;java # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题3、字体 …

Webpack 的 sass-loader 在生产模式下最小化 CSS 问题

学习webpack时候我发现一个问题&#xff1a; 将mode 改为production模式后&#xff0c;生成的css会被压缩了&#xff0c;但是我并没有引入CssMinimizerPlugin插件&#xff0c;然后我试着将optimization.minimize 设置为false&#xff0c;测试是否为webpack自带的压缩&#xff0…