ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【前缀树】写一个敏感词过滤器

2021-10-09 10:30:21  阅读:156  来源: 互联网

标签:结点 前缀 tempNode 敏感 过滤 过滤器 指针


1.什么是敏感词过滤

这其实是一个很常见的功能,随处可见以至于你可能都没关注过,基本上在有评论的地方都会有它的身影。

举例来说,你打游戏和别人对喷的时候,是不是一些脏话发不出去哈哈,这些词汇会用***代替。

再比如,一些话题和视频下评论政治敏感词、色情低俗等等都是需要过滤的。

那这种过滤是怎么实现的呢?

那就不得不聊一聊一种数据结构了,那就是前缀树。

2.前缀树

前缀树是树的一种扩展类型,其实结构本身还是挺简单的,难点在于对这个结构制定的算法。

它的结构是按照敏感词来构建的,假设abc,be,bf这三个词是敏感词,

则前缀树的结构图如下:

根结点为空,每个子结点存放敏感词的字母,而从根结点出发到叶子结点这条路径表示为一个敏感词。

这样看来,给定一个字符串,如果其中有一部分能和一条路径匹配,则匹配的那部分就是敏感词,可以把该部分字符串替换为**。

比如给定字符串xwyabckk,其中有字符串abc和前缀树的一条路径匹配,可以把匹配的部分替换为***,那么过滤后的字符串为xwy***kk。

可是,假如abc和ab都是敏感词呢?那ab这个词是不会走到叶子结点的,就不被认为是敏感词了。

怎么改进呢?很简单,每个结点加一个标识位,也就是一个布尔值。

标识位为true表示到这个结点为敏感词,为false则表示从根结点到这个结点的路径表示的词不是敏感词。

这个标识位在图里用五角星表示了:

3.如何用代码实现前缀树和过滤算法

核心思想是用三个指针来实现过滤。

指针root指向前缀树的根结点,指针begin指向文本的起始位置,指针position指向过滤文本的最后位置。

图示如下:

在进行过滤算法前,首先需要创建这颗前缀树。

先创建前缀树的数据结构,这里拿java实现,其他语言大同小异。

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

  // 子节点(key是下级字符,value是下级节点)
  private Map<Character, TrieNode> subNodes = new HashMap<>();

  public boolean isKeywordEnd() {
    return isKeywordEnd;
  }

  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);
  }

有了前缀树的数据结构,接下来就要构建这颗树了,在此之前,你要先明确过滤的词有哪些,可以放到一个文本文件中读取。

然后根据敏感词构建前缀树。这里便写一个差入敏感词构建前缀树的方法:

// 将敏感词添加到前缀树
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);
    }
  }
}

这棵树构建完毕,接下来就是写如何过滤的算法了,这里写一个函数。

这个函数负责过滤,参数为输入的需要过滤的字符串,输出为过滤好的字符串。

/**
	* 过滤敏感词
  * @param text 待过滤文本
  * @return 过滤后的文本
  */
public String filter(String text) {
  if(StringUtils.isBlank(text)) {
    return null;
  }
  // 指针1:指向树的节点
  TrieNode tempNode = rootNode;
  // 指针2:指向敏感词的开始索引
  int begin = 0;
  // 指针3:指向敏感词的结尾索引
  int position = 0;
  // 存放结果
  StringBuilder sb = new StringBuilder();

  while (position < text.length()) {
    char c = text.charAt(position);
    // 跳过符号 isSymbol判断该字符是否为符号
    // 这里是为了防止用户在敏感词中间输入符号 例如【傻、逼】,中间有符号也可以过滤
    if(isSymbol(c)) {
      // 若指针1处于根节点,将词符号计入结果,指针2向下走一步
      if(tempNode == rootNode) {
        sb.append(c);
        begin++;
      }
      // 无论符号在开头或中间,指针3都向下走一步
      position++;
      continue;
    }
    // 检查下级节点
    tempNode = tempNode.getSubNode(c);
    if(tempNode == null) {
      // 以begin开头的字符串不是敏感词
      sb.append(text.charAt(begin));
      // 进入下一个位置
      position = ++begin;
      // 指针1重新指向根节点
      tempNode = rootNode;
    } else if(tempNode.isKeywordEnd()) {
      // 发现敏感词begin-position,将其替换
      sb.append(REPLACEMENT);
      // 进入下一位置
      begin = ++position;
      // 指针1重新指向根节点
      tempNode = rootNode;
    } else {
      // 继续检查下一个字符
      position++;
    }
  }
  // 将最后一批字符计入结果
  sb.append(text.substring(begin));
  return sb.toString();
}

最终,一个敏感词过滤器就写好了。

当然了,这里只是最简单的过滤器,实际业务中要考虑很多情况,不过都是在此基础之上做改动。


最后,欢迎关注公众号「码农小奎」,分享编程经历和经验,一直在路上

标签:结点,前缀,tempNode,敏感,过滤,过滤器,指针
来源: https://blog.csdn.net/weixin_45846837/article/details/120665482

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有