首页 > 编程语言 >敏感词过滤算法实现(前缀树)

敏感词过滤算法实现(前缀树)

时间:2023-08-14 19:56:12浏览次数:35  
标签:begin 前缀 tempNode 敏感 算法 过滤 字符串 position 节点

前缀树

前缀树是N叉树的一种特殊形式,也叫Trie、字典树、查找树。通常来说,一个前缀树是用来存储字符串的。前缀树的每一个节点代表一个 字符串 ( 前缀 )。每一个节点会有多个子节点,通往不同子节点的路径上有着不同的字符。子节点代表的字符串是由节点本身的原始字符串,以及 通往该子节点路径上所有的字符组成的。

前缀树常用于实现字符串检索、词频统计、字符串排序等操作,它的特点有:查找效率高,消耗内存大

前缀树的数据结构比较简单,关键字段为关键字结束标识和子节点,主要方法为关键字段的get和set方法。

 //前缀树
  private class TrieNode{
    // 关键字结束标识
    private boolean isKeywordEnd = false;

    //子节点 key:下级字符 value: 下级节点
    private Map<Character,TrieNode> subNodes =new HashMap<>();
    //get关键字结束标识
    public boolean isKeywordEnd() {
      return isKeywordEnd;
    }
    //set关键字结束标识
    public void setKeywordEnd(boolean keywordEnd) {
      isKeywordEnd = keywordEnd;
    }
    //添加子节点
    public void addSubNode(Character c, TrieNode node) {
      subNodes.put(c, node);
    }

    //获取子节点
    public TrieNode getSubNode(Character c) {
      return subNodes.get(c);
    }
  }

image-20230216201045963

初始化前缀树

我们的敏感词是不断发生变化增长,所以我们把敏感词持久化到一份txt文件中,当然也可以保存到数据库,实现敏感词的动态更新。同时为了提高程序运行效率我们会在项目启动时初始化加载前缀树,这里用到了 @PostConstruct注解,代码如下:

 @PostConstruct
  public void init() {
    try (
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    ) {
      String keyword;
        //逐行读取敏感词
      while ((keyword = reader.readLine()) != null) {
        // 添加到前缀树
        this.addKeyword(keyword);
      }
    } catch (IOException e) {
      log.error("加载敏感词文件失败: " + e.getMessage());
    }
  }

添加敏感词

由于我们设置txt中每行一个敏感词,我们可以直接将读取到的keyword传入addKeyword方法。

 // 将一个敏感词添加到前缀树中
  private void addKeyword(String keyword) {
      //初始节点、根节点
    TrieNode tempNode = rootNode;
    for (int i = 0; i < keyword.length(); i++) {
      char c = keyword.charAt(i);
      TrieNode subNode = tempNode.getSubNode(c);

      if (subNode == null) {
        // 初始化子节点
        subNode = new TrieNode();
        tempNode.addSubNode(c, subNode);
      }

      // 指向子节点,进入下一轮循环
      tempNode = subNode;

      // 设置结束标识
      if (i == keyword.length() - 1) {
        tempNode.setKeywordEnd(true);
      }
    }
  }
    public String sensitiveWordFilter(String text) {
        //若是空字符串 返回空
        if (StringUtils.isBlank(text)) {
            return null;
        }
        // 根节点
        // 每次在目标字符串中找到一个敏感词,完成替换之后,都要再次从根节点遍历树开始一次新的过滤
        TrieNode tempNode = rootNode;
        // begin指针作用是目标字符串每次过滤的开头
        int begin = 0;
        // position指针的作用是指向待过滤的字符
        // 若position指向的字符是敏感词的结尾,那么text.subString(begin,position+1)就是一个敏感词
        int position = 0;
        //过滤后的结果
        StringBuilder result = new StringBuilder();

        //开始遍历 position移动到目标字符串尾部是 循环结束
        while (position < text.length()) {
            // 最开始时begin指向0 是第一次过滤的开始
            // position也是0
            char c = text.charAt(position);

            //忽略用户故意输入的符号 例如嫖※娼 忽略※后 前后字符其实也是敏感词
            if (isSymbol(c)) {
                //判断当前节点是否为根节点
                //若是根节点 则代表目标字符串第一次过滤或者目标字符串中已经被遍历了一部分
                //因为每次过滤掉一个敏感词时,都要将tempNode重新置为根节点,以重新去前缀树中继续过滤目标字符串剩下的部分
                //因此若是根节点,代表依次新的过滤刚开始,可以直接将该特殊符号字符放入到结果字符串中
                if (tempNode == rootNode) {
                    //将用户输入的符号添加到result中
                    result.append(c);
                    //此时将单词begin指针向后移动一位,以开始新的一个单词过滤
                    begin++;
                }
                //若当前节点不是根节点,那说明符号字符后的字符还需要继续过滤
                //所以单词开头位begin不变化,position向后移动一位继续过滤
                position++;
                continue;
            }
            //判断当前节点的子节点是否有目标字符c
            tempNode = tempNode.getSubNode(c);
            //如果没有 代表当前beigin-position之间的字符串不是敏感词
            // 但begin+1-position却不一定不是敏感词
            if (tempNode == null) {
                //所以只将begin指向的字符放入过滤结果
                result.append(text.charAt(begin));
                //position和begin都指向begin+1
                position = ++begin;
                //再次过滤
                tempNode = rootNode;
            } else if (tempNode.isWordEnd()) {
                //如果找到了子节点且子节点是敏感词的结尾
                //则当前begin-position间的字符串是敏感词 将敏感词替换掉
                result.append(REPLACE_WORD);
                //begin移动到敏感词的下一位
                begin = ++position;
                //再次过滤
                tempNode = rootNode;
                //&& begin < position - 1
            } else if (position + 1 == text.length()) {
                //特殊情况
                //虽然position指向的字符在树中存在,但不是敏感词结尾,并且position到了目标字符串末尾(这个重要)
                //因此begin-position之间的字符串不是敏感词 但begin+1-position之间的不一定不是敏感词
                //所以只将begin指向的字符放入过滤结果
                result.append(text.charAt(begin));
                //position和begin都指向begin+1
                position = ++begin;
                //再次过滤
                tempNode = rootNode;
            } else {
                //position指向的字符在树中存在,但不是敏感词结尾,并且position没有到目标字符串末尾
                position++;
            }
        }
        return begin < text.length() ? result.append(text.substring(begin)).toString() : result.toString();
    }

标签:begin,前缀,tempNode,敏感,算法,过滤,字符串,position,节点
From: https://www.cnblogs.com/touchTomorrow/p/17128453.html

相关文章

  • 类欧几里得算法
    类欧几里得算法定义\[\displaystyle\begin{aligned}f(a,b,c,n)&=\sum\limits_{i=0}^{n}\left\lfloor\dfrac{ai+b}{c}\right\rfloor\\g(a,b,c,n)&=\sum\limits_{i=0}^{n}{\left\lfloor\dfrac{ai+b}{c}\right\rfloor}^2\\h(a,b,c,n)&......
  • 使用布隆过滤器求两个大文件交集
    随着互联网的发展,大数据应用越来越多。如何在内存有限的条件下,对超大规模数据进行效率处理,是一个值得探讨的问题。本文将以求两个文件共同元素为例,探讨一种基于布隆过滤器的高效算法。问题描述假设有文件A和文件B,各包含50亿个url,每个url64字节,内存限制为4G。要求找出A和B......
  • 数据结构(哈夫曼树):判定编码方案是否为前缀编码
    前缀编码定义:(字符集中)任一编码都不是其它字符的编码的前缀(字符集中)任一编码都不是其它字符的编码的前缀(字符集中)任一编码都不是其它字符的编码的前缀重要的话说三遍!例:(1)找出下面不是前缀编码的选项A{1,01,000,001}B{1,01,011,010}C{0,10,110,11}D{0,1,00,11}第一步:看A中的第一个数1,看看其他......
  • npm 更改package.json 中依赖包前缀
    ~会匹配最近的小版本依赖包,比如~1.2.3会匹配所有1.2.x版本,但是不包括1.3.0^会匹配最新的大版本依赖包,比如^1.2.3会匹配所有1.x.x的包,包括1.3.0,但是不包括2.0.0 *这意味着安装最新版本的依赖包 推荐使用~ npmconfigsetsave-prefix='~'......
  • 编程题算法总结
    求最大公约数最小公倍数最大公约数辗转相除法大的a除小的b,得到余数如果是0,那么b就是最大公约数,否则就取余数做那个小的,本来的b就成了大的继续操作。intn,m;//辗转相除法,ab最大公约数=ab余数和b的最大公约数intyu,a,b;a=n>m?n:m;b=n>m?m:n......
  • 位运算 学习笔记【C++ 算法竞赛】
    大家好,欢迎来到我的第一篇博客位运算和移位运算作为计算机的基本运算之⼀,其都是对⼆进制位进⾏操作。作为近年算法竞赛笔试较热门的考点,它能够快捷地完成特定的应用。掌握它是⾮常有必要的。以下是目录:目录1.位运算的优先级2.左移运算<<、右移运算>>2.1运算规则:2.2应用:......
  • 路径规划算法:基于人工蜂鸟优化的机器人路径规划算法- 附matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • 路径规划算法:基于协作搜索优化的机器人路径规划算法- 附matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • 【题解】洛谷 P9532 [YsOI2023] 前缀和
    原题链接【LGR-151-Div.2】洛谷8月月赛II&YsOI2023T1解题思路设有一序列a,其中a1 =a2,第k(≥3) 项为前k-1项的前缀和。可以发现前q项分别为第一项的20 倍,20 倍,21 倍,22 倍,23 倍…2q-3 倍,2q-2 倍。扩展到整个序列中,可得第i( ≥ 3)项一定为2i-2a1。......
  • 深入解析美颜SDK:算法、效果与实现
    在当今数字化社会中,图像处理和美化技术已经成为了许多应用领域的重要组成部分,尤其在视频直播领域,美颜技术更是无处不在。直播美颜SDK作为一种集成的软件工具包,为开发者和应用提供了强大的美颜功能。一、算法原理磨皮算法通过降低图像中的高频细节,达到皮肤更光滑的效果。美白算法调......