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

news2024/12/29 10:28:25

文章目录

    • 前缀树介绍
    • 节点
    • 初始化前缀树
    • 添加敏感词
    • 删除敏感词
    • 敏感词过滤
    • 代码实现

前缀树介绍

前缀树(Trie),也称为字典树或前缀字典树,是一种特殊的多叉树数据结构。它用于高效地存储和检索字符串集合。以下是前缀树的常见数据结构和相关术语:

  1. 节点(Node):每个节点包含一个字符和指向子节点的链接。通常使用散列表、数组或其他数据结构来存储子节点链接。
  2. 根节点(Root Node):前缀树的顶层节点,没有父节点。
  3. 子节点(Child Node):一个节点的直接后代节点。
  4. 叶节点(Leaf Node):没有后续节点的节点,用来表示字符串的结束字符。
  5. 边(Edge):连接相邻节点的链接,每个边上都标有一个字符。
  6. 树的高度(Height):从根节点到最深叶节点的最长路径。
  7. 前缀(Prefix):从根节点到任意节点的路径,表示一个字符串的前缀。

基于这些术语,前缀树的基本操作包括插入、搜索、删除和前缀匹配。通过构建一个前缀树,可以实现高效地存储和检索大量字符串,快速判断一个字符串是否是集合中的成员,并找到具有给定前缀的所有字符串。

节点

前缀树(Trie)的节点结构通常由两部分组成:节点值和子节点集合。子节点集合通常使用散列表、数组或其他数据结构。

我们还需要使用 endOfWord 标识该节点是否为一个单词的结尾。如果某个节点的 isEndOfWordtrue,则表示从根节点到当前节点的路径构成了一个完整的单词,即过滤词。

下面是一个示例的前缀树节点结构:

class TrieNode {
    private Map<Character, TrieNode> children; // 子节点集合
    private boolean endOfWord; // 标识是否为单词的结尾

    public TrieNode() {
        children = new HashMap<>();
        endOfWord = false;
    }

    public Map<Character, TrieNode> getChildren() {
        return children;
    }

    public boolean isEndOfWord() {
        return endOfWord;
    }

    public void setEndOfWord(boolean endOfWord) {
        this.endOfWord = endOfWord;
    }
}

通过这种节点结构,我们可以链接节点以形成一个树形结构,每个节点代表一个字符。通过不断地添加子节点,我们可以构建出完整的前缀树,用于高效地存储和搜索字符串集合。

初始化前缀树

前缀树有一个根节点(Root Node)作为起始节点。前缀树的初始化过程如下:

  1. 创建一个空的前缀树,即一个根节点。

  2. 遍历字符串集合,逐个插入字符串到前缀树中。

  3. 对于每个字符串,从根节点开始,检查当前字符是否已经存在于当前节点的子节点中。

    • 如果存在,移动到该子节点,并继续处理下一个字符。
    • 如果不存在,创建一个新的子节点,将当前字符添加到子节点中,并移动到该子节点。
  4. 重复步骤3,直到字符串的所有字符都被插入到前缀树中。

  5. 重复步骤2-4,直到字符串集合中的所有字符串都被插入到前缀树中。

通过上述初始化过程,我们可以构建一个包含所有字符串集合中字符串的前缀树。这样,在后续的搜索或过滤操作中,我们可以利用前缀树的特性来提高效率,快速地查找和处理字符串。

添加敏感词

我们可以将一个敏感词插入到前缀树中。每个字符都对应着一个节点,通过连接节点的方式,形成了一个表示敏感词的路径。最后一个字符对应的节点被标记为敏感词的结尾,以便在后续的搜索操作中判断是否存在完整的敏感词。前缀树中添加一个敏感词的过程如下:

  1. 创建一个指向根节点的 current 变量,用于表示当前节点。

  2. 遍历敏感词的每个字符。

  3. 对于每个字符,在当前节点的子节点集合中查找是否存在字符对应的子节点。

    • 如果存在子节点,则将 current 更新为该子节点;
    • 如果不存在子节点,则使用创建一个新的子节点,并将 current 更新为该新节点。
  4. 重复步骤3,直到遍历完整个敏感词的所有字符。

  5. 将最后一个字符所对应的节点(即单词的末尾字符)设置为单词的结尾,将其 endOfWord 属性设置为 true,表示该单词在前缀树中存在。

删除敏感词

要删除前缀树中的敏感词,可以采用递归的方式遍历前缀树来查找待删除的敏感词。默认从根节点开始,并通过字符索引递归地将路径沿着前缀树向下移动。

  • 如果到达了敏感词的最后一个字符,表示找到了待删除的单词节点。将该节点的 endOfWord 属性设置为 false,表示该单词不再存在于前缀树中,并判断当前节点是否有其他子节点,如果没有子节点则删除当前字符对应的子节点。
  • 如果还没有到达敏感词的最后一个字符,继续向下遍历前缀树,直到找到敏感词的最后一个字符。如果递归地删除该字符之后,发现当前节点没有其他子节点了,则可以将当前字符对应的子节点从父节点的子节点集合中删除,保持树的结构和有效性。

敏感词过滤

假设我们已经初始化完成一个前缀树,其中包含以下敏感词:「bad」、「bar」、「byd」、「cao」,我们对「This is a bad example. The bar is closed.」进行过滤:

  1. 逐个字符遍历「This is a bad example. The bar is closed.」:
    • 第一个字符「T」与前缀树匹配不上,因此将其添加到过滤后文本中。
    • 第二个字符「h」与前缀树匹配不上,因此将其添加到过滤后文本中。
    • 第三个字符「i」与前缀树匹配不上,因此将其添加到过滤后文本中。
    • 第四个字符「s」与前缀树匹配不上,因此将其添加到过滤后文本中。
  2. 由于字符「 」(空格)不是字母或数字,直接添加到过滤后文本中,重置前缀树的当前节点为根节点。
  3. 重复步骤1和2,直到遍历完整个原始文本。
  4. 遍历到「bad」时:
    • 第一个字符「b」与前缀树节点 b 匹配,继续处理下一个字符。
    • 第二个字符「a」与前缀树节点 a 匹配,继续处理下一个字符。
    • 第三个字符「d」与前缀树节点 d 匹配。
  5. 当前单词为「bad」,由于结束符号「d」的 endOfWord 属性为 true,代表这是一个敏感词,将当前单词替换为「***」并添加到过滤后文本中。
  6. 「bar」匹配流程相同。
  7. 最终过滤后的文本为:「This is a *** example. The *** is closed.」

通过前缀树,我们可以高效地找到和替换敏感词,将其过滤或标记为合适的内容。这样能够保护用户免受敏感词的影响。

代码实现

代码实现如下:

import java.util.HashMap;
import java.util.Map;

public class TrieFilter {
    private TrieNode root;

    public TrieFilter() {
        root = new TrieNode();
    }

    // 添加敏感词
    public void addWord(String word) {
        TrieNode current = root;
        for (char c : word.toCharArray()) {
            current = current.getChildren().computeIfAbsent(c, k -> new TrieNode());
        }
        current.setEndOfWord(true);
    }

    // 删除敏感词
    public void deleteWord(String word) {
        deleteWord(root, word, 0);
    }

    private boolean deleteWord(TrieNode current, String word, int index) {
        if (index == word.length()) {
            if (!current.isEndOfWord()) {
                return false; // 单词不存在于前缀树中,无需删除
            }
            current.setEndOfWord(false); // 将当前节点标记为非单词结尾
            return current.getChildren().isEmpty(); // 判断当前节点是否有其他子节点
        }

        char c = word.charAt(index);
        TrieNode child = current.getChildren().get(c);
        if (child == null) {
            return false; // 单词不存在于前缀树中,无需删除
        }

        boolean shouldDeleteChild = deleteWord(child, word, index + 1);

        if (shouldDeleteChild) {
            current.getChildren().remove(c); // 删除当前字符对应的子节点
            return current.getChildren().isEmpty(); // 判断当前节点是否有其他子节点
        }

        return false;
    }

    // 敏感词过滤
    public String filter(String text) {
        StringBuilder filteredText = new StringBuilder();
        StringBuilder currentWord = new StringBuilder();
        TrieNode current = root;

        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);

            if (Character.isLetterOrDigit(c)) {  // 字母或数字,继续匹配
                current = current.getChildren().get(Character.toLowerCase(c));
                if (current != null) {
                    currentWord.append(c);
                    if (current.isEndOfWord()) {
                        currentWord.replace(0, currentWord.length(), "***");
                    }
                } else {
                    filteredText.append(currentWord);
                    filteredText.append(c);
                    currentWord.setLength(0);
                    current = root;
                }
            } else {  // 非字母或数字,结束当前单词匹配
                filteredText.append(currentWord);
                filteredText.append(c);
                currentWord.setLength(0);
                current = root;
            }
        }

        filteredText.append(currentWord);
        return filteredText.toString();
    }

    // 节点结构
    class TrieNode {
        private Map<Character, TrieNode> children;
        private boolean endOfWord;

        public TrieNode() {
            children = new HashMap<>();
            endOfWord = false;
        }

        public Map<Character, TrieNode> getChildren() {
            return children;
        }

        public boolean isEndOfWord() {
            return endOfWord;
        }

        public void setEndOfWord(boolean endOfWord) {
            this.endOfWord = endOfWord;
        }
    }
}

使用示例:

public static void main(String[] args) {
  TrieFilter filter = new TrieFilter();
  filter.addWord("敏感词1");
  filter.addWord("敏感词2");
  filter.deleteWord("敏感词2");

  String text = "这是一段包含敏感词1和敏感词2的文本";
  String filteredText = filter.filter(text);
  System.out.println(filteredText);
}

输出结果:

这是一段包含***和敏感词2的文本

在上述示例中,我们创建了一个 TrieFilter 类来实现敏感词过滤功能。使用 addWord 方法将敏感词添加到前缀树中,然后使用 filter 方法对文本进行过滤,将匹配到的敏感词替换为 ***,使用 deleteWord 方法从前缀树中删除敏感词。

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

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

相关文章

verilog实现led闪烁

文章目录 verilog实现led闪烁一、介绍二、代码三、仿真代码四、仿真结果五、总结 verilog实现led闪烁 一、介绍 使用verilog实现代码&#xff0c;实现led闪烁&#xff0c;每间隔200ms进行切换led灯 二、代码 module led (input wire clk,input wire rstn,output wire[3:0] …

深入解析向量数据库:定义、原理和应用的全面指南

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

C++智能指针(3/3)

目录 上一节内容 share_ptr用法 share_ptr指针可以用于上一节所说的错误 例子&#xff08;类定义&#xff09; 主函数代码 执行的结果 解释说明 share_ptr 相关构造 空的share指针可以指向其他相同类型的变量来进行托管 可以shared_ptr< T > sp2(new T())也可以s…

RocketMQ高阶使用

RocketMQ高阶使用 1. 流程 2. 探讨功能点 RocketMQ的顺序消息消息投递策略消息保障 3. 顺序消息 3.1 顺序类型 3.1.1 无序消息 无序消息也指普通的消息&#xff0c;Producer 只管发送消息&#xff0c;Consumer 只管接收消息&#xff0c;至于消息和消息之间的顺序并没有保证…

macOS搭建C++开发环境CLion

首先我是一个java开发者&#xff0c;最近对C产生点兴趣。想开发点C程序玩一玩。 下载IDE 本人是java开发者&#xff0c;习惯使用IDEA了。所以也下载jetbrains的C开发工具:clion 下载地址&#xff1a; https://www.jetbrains.com/clion/download/#sectionmac Hello world Fi…

利用ArcGIS Pro制作三维效果图

1、新建工程 打开Arcgispro,新建工程,这里我们要用到的模板为全局场景。 2、添加数据 这里添加的数据需要有一个字段内容是数值的,这个字段也是接下来要进行拉伸的字段。 3、高度拉伸 数据添加进来后,如下图所示,这时图层处于2D图层里。 这时我们点中该图层,回到菜单栏…

微服务系列文章 之SpringBoot之定时任务详解

序言 使用SpringBoot创建定时任务非常简单&#xff0c;目前主要有以下三种创建方式&#xff1a; 一、基于注解(Scheduled)二、基于接口&#xff08;SchedulingConfigurer&#xff09; 前者相信大家都很熟悉&#xff0c;但是实际使用中我们往往想从数据库中读取指定时间来动态…

天眼使用指南--分析平台

#天眼分析平台 提供全面的溯源分析能力&#xff0c;涵盖图中模块。负责存储日志&#xff0c;分为三类&#xff0c;告警日志 告警日志&#xff1a;来自探针和沙箱的告警&#xff0c;探针的告警可以记录双向完整对话&#xff0c;如果网络流量中没有恶意信息&#xff0c;就会储存…

windows Server 2008 R2服务器IIS环境启用TLS 1.2

windows Server 2008 R2服务器IIS环境启用TLS 1.2&#xff0c;配置TLS1.2 分为2步, 添加TLS配置和禁用老的SSL版本&#xff0c;提供两种方法, 选择其中一种就行了&#xff0c;手动设置 打开注册表&#xff0c;运行regedit&#xff0c;找到 HKEY_LOCAL_MACHINE\SYSTEM\CurrentCo…

【hadoop】在linux上设置Hadoop的环境变量

设置Hadoop的环境变量 解压压缩包编辑环境变量激活环境变量 解压压缩包 使用下面命令对hadoop的压缩包进行解压 tar -zxvf hadoop-2.7.3.tar.gz -C ~/training/编辑环境变量 在linux中&#xff0c;~/.bash_profile文件是设置环境变量的文件&#xff0c;我们使用vi进行编辑。…

Verdi之波形展示nWave

6.nWave 6.1 添加波形文件 1.打开nWave界面&#xff0c;具体操作如下&#xff1a; 2.正式添加波形&#xff0c;使用快捷键G或者点击以下图标&#xff0c;选择需要的信号。 也可以在 n Trace中选中信号后&#xff0c;鼠标中键拖拽&#xff0c;或者ctrlw进行添加&#xff1b; 6…

Dreamweaver批量替换所有超链接替换成#

需求&#xff1a;想要将页面所有链接地址替换为#。 方法一 CTRLF打开“查找和替换”&#xff0c;勾选“使用正则表达式” 查找 href"([\s\S]*?)" 替换为 href"#" 副作用&#xff1a;样式表链接地址也会被替换为#&#xff0c;需提前备份。 方法二 也可以查…

CAN总线(二)CAN协议的帧格式(一文看懂CAN的报文结构)

如果只是使用CAN进行CAN通讯,可以粗略看下以下内容,主要了解下数据字段,但了解一下其他内容有助于使用CAN通讯。 一、CAN总线协议规范 CAN报文有两种不同的格式:标准格式和扩展格式,前者的标志符长度是11位,而后者的标志符长度可达29位。 CAN协议的2.0A版本规定CAN控制…

Git -> 创建第一个本地repo

创建一个本地仓库及提交文件 打开Git Bash执行以下命令 // 切换至d盘 cd d: // 新建文件夹 mkdir my_first_local_repo // 切换至新建文件夹 cd my_first_local_repo假设my_first_local_repo文件夹下有以下文件 初始化git仓库 // 在当前文件夹初始化git仓库 git init.gi…

【stable diffusion】保姆级入门课程-Stable diffusion(SD)介绍与安装

目录 0.学前准备 1.什么是AI绘画 2.当前主流的AI绘画工具 3.什么是SD(stable diffusion) 4.SD能做什么 1.文生图 2.图生图 3.AI换模特&#xff0c;背景 5.使用stable diffusion配置要求 6.环境配置与安装 需要注意的地方&#xff1a; 扩展知识&#xff1a; 1.pyth…

Linux学习之环境变量配置文件

配置文件的执行先后顺序如下&#xff1a; /etc/profile $HOME/.bash_profile $HOME/.bashrc /etc/bashrc vim /etc/profile&#xff0c;把echo "/etc/profile"写到第一行&#xff0c;head -n 1 /etc/profile看一下/etc/profile里边第一行内容。 vim $HOME/.bash_pr…

工作:三菱PLC之CC-Link IE Field Network通讯知识及应用

工作&#xff1a;三菱PLC之CC-Link IE Field Network通讯知识及应用 一、理论 1. 简介连接 CC-LINK-IE通讯分别有 CC-Link IE TSN&#xff0c;CC-Link IE Control Network&#xff0c;CC-Link IE Field Network&#xff0c;CC-Link IE Field Network Basic几种形式&#xff…

38译码器

文章目录 38译码器一、38译码器介绍二、项目代码三、仿真代码四、仿真结果 五、总结 38译码器 一、38译码器介绍 38译码器是一种常用的逻辑电路元件&#xff0c;用于将一个3位二进制输入编码转换成8个输出信号之一。它具有多个输入引脚和多个输出引脚。 通常&#xff0c;38译…

Linux下Lua和C++交互

前言 lua&#xff08;wiki 中文 官方社区&#xff1a;lua-users&#xff09;是一门开源、简明、可扩展且高效的弱类型解释型脚本语言。 由于其实现遵循C标准&#xff0c;它几乎能在所有的平台&#xff08;windows、linux、MacOS、Android、iOS、PlayStation、XBox、wii等&…

【Modbus】Modbus协议讲解

Modbus协议讲解 前言一、串口通讯简介二、RS485串口通讯RS485通讯标准的由来&#xff08;了解&#xff09;RS485特点RS-485终端电阻的选择 三、Modbus协议四、Modbus报文范例 前言 本篇是我参加工作培训时&#xff0c;作为记录笔记用的&#xff0c;因此写的方式不会像前面那些系…