DFA的敏感词过滤实现

DFA在wiki上面的解释是“确定有限状态自动机或确定有限自动机(deterministic finite automaton, DFA)是一个能实现状态转移的自动机”。这里我直白的一个自我理解就是通过一个事先维护好的状态集,自动进行一个判断转移到下一个状态。

状态转移

通过上面的图,我们来解释一个下大致的算法思路:

  • Q:是一个所有的状态的集合(包含了状态A1,A2,A3,B1,B2....D3)
  • I : 是我们输入数据
  • transfer: 是我们通过I输入的数据,获得到起始状态之后,进行转移到下一个状态的工具。

所以,大致的思路,就是通过输入的数据I([a,b,c,d])然后进行对Q状态集查询起始的状态,由I[0] = a开始作为一个入参去Q中寻找。假设找到了A1,然后,这个时候,我们的入参I还没操作完其他数据,这个时候,我们就要将状态转移到A1的下一个状态并且和我们的I[1]进行判断,看看是否能够成功的转移到A2上。

  • I[0] -> A1
  • I[1] -> A2
    以此类推,当我们的状态链路不能往下的时候,标明了我们的输入I中,存在一个A1....AN的这么一个状态链路(在我们进行一个敏感词的时候,这个链路就可以理解为整个敏感词)。

那么,通过上面的流程,我们把它引入到我们的敏感词的实现上:

  • 首先,我们的敏感词,一个敏感词,就可以理解为一个状态的链路,然后每个节点的状态就代表为一个字。
  • I 这里就代表我们输入要进行判断是否包含敏感词的目标语句
  • A1,B1, C1, D1,这些都是表示一个敏感词的开始的第一个字。
  • 然后我们循环从I的第一次字开始判断是否在A1到D1中存在这样一个状态
  • 如果存在,就继续往下,查询下一个状态是否在链路上,如果不在链路上,这个时候就要及时的跳出,去头部开始重新的查找。

具体的代码,我们通过Java的HashMap来进行一个实现:

  1. 先是一个构造我们的状态集合Q的代码:
  private static void init(Set<Sensitive> sensitiveWordSet) {

    Iterator<Sensitive> iterator = sensitiveWordSet.iterator();

    while (iterator.hasNext()) {
      Sensitive sensitive = iterator.next();

      char[] text = sensitive.getText().toCharArray();

      Map tempMap = hashMap;
      int index = 1;      // 标明是第几个文字,用于遮掩敏感词的时候,进行一个回溯.
      for (int i=0; i<text.length; i++) {
        char item = text[i];
        if (!tempMap.containsKey(item)) {
          DFANode dfaNode = new DFANode();
          dfaNode.setIndex(index);
          dfaNode.setSub(new HashMap());
          // 用于判断是否是最终文字
          dfaNode.setEnd(i == (text.length-1));     // end?

          tempMap.put(item, dfaNode);
          tempMap = dfaNode.getSub();
        } else {
          DFANode subNode = (DFANode) tempMap.get(item);
          tempMap = subNode.getSub();
        }
        index++;
      }
    }
  }

  private static void loadFile() throws IOException {
    File file = new File(
        DFATextFilter.class.getClassLoader().getResource("sensitive.txt").getFile());

    List<String> list = Files.readLines(file, Charset.forName("utf-8"));

    Set<Sensitive> sensitiveWordSet = new HashSet<>(40);

    list.forEach(item -> {
      sensitiveWordSet.add(Sensitive.builder().text(item).build());
    });

    init(sensitiveWordSet);
  }

@Data
@Builder
public class Sensitive {

  private String text;
}

@Data
public class DFANode {

  private boolean end;

  private int index;    // 标明单单在敏感字中的位置

  private Map sub;
}

这一部分的代码就是遍历我们的文件,然后逐行读取。

  1. 下面是一个过滤的代码:
  private  FilterResult check(String str, boolean mask) {
    Map tempCheckMap = hashMap;
    boolean exist = false;
    char[] chars = str.toCharArray();
    for (int i=0; i<chars.length; i++) {
      if (tempCheckMap.containsKey(chars[i])) {
        DFANode subNode = (DFANode) tempCheckMap.get(chars[i]);

        if (subNode.isEnd()) {
          // 遮掩敏感词
          if (mask) {
            int length = subNode.getIndex();
            for (int j=0; j<length; j++) {
              chars[i-j] = 'X';
            }
          }

          exist = true;
          str = String.valueOf(chars);
        } else {
          tempCheckMap = subNode.getSub();
        }
      } else {
//    从链路跳出从头开始,或者是第一步
        tempCheckMap = hashMap;
        if (tempCheckMap.containsKey(chars[i])) {
          DFANode subNode = (DFANode) tempCheckMap.get(chars[i]);

          if (subNode.isEnd()) {
            if (mask) {
              int length = subNode.getIndex();
              for (int j=0; j<length; j++) {
                chars[i-j] = 'X';
              }
            }
            exist = true;
            str = String.valueOf(chars);
            tempCheckMap = hashMap;
          } else {
            tempCheckMap = subNode.getSub();
          }
        }
      }
    }

    return FilterResult.builder().sensitive(exist).str(str).build();
  }

@Data
@ToString
@Builder
public class FilterResult {

  private String str;

  private boolean sensitive;
}

整体的代码实现还是比较简单,这里也就简单的提供一个思路。完整的代码查看链接: https://github.com/songshuangkk/DFA-Filter/tree/master

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279

推荐阅读更多精彩内容

  • 好久没更新,这是一篇长文。也是一篇比较硬的文章,比较烧脑。在硬长文里,可能很难找到这么有趣的。在有趣的文章里,可能...
    kamidox阅读 1,861评论 1 16
  • 有时候在我看来别人干啥都是正常都是应该,而我自己就是跳梁小丑。在搞笑。
    骜默阅读 136评论 0 0
  • 写完这篇文章,我就不再公布随意记录的文章了。因为我喜欢简书 01 第一次知道简书是在微信的一篇推文里,作者推荐了许...
    莫流年阅读 325评论 7 5
  • 昨天我夜里咳嗽的很厉害,今天早上,吃完早饭,就去医院了,医生让我先去验血,然后再去刮痧,拔罐。验血很疼,首...
    陈泉妡阅读 128评论 0 0
  • 今天的分别好似已盼望很久很久,但所有的话语已经不能代替昨日的过往。过去的痛苦和欢乐都将随着今天画上圆满的句号。 从...
    鸣鸥阅读 1,333评论 18 23