TrieTree+敏感词过滤

昨天自己写了一个敏感词过滤算法。
TrieTree是跟着左神学的,因为最近在做网页,不可避免的后台会遇见敏感词过滤,这个在大家的生活中应该很常见,比如游戏,以及随意的一个网站都会有。
我是依靠自己的理解写的,必定有很多考虑不周到的地方。所以看的话,大家当个参考就可以了。
先了解一下前缀树,TrieTree,也可称为字典树。
最基本的字典树是由一个boolean类型的变量和一个TrieNode[26]组成的。
boolean负责检查单词的结尾,比如说abc插入以后,要检测是否有ab,但是如果没有这个boolean变量,则无法确定b是不是结尾。正确的操作是这样的,插入a 然后看a下面有没有b,没有则插入,有就直接看b,依次类推。那么为啥要这种Tree,顾名思义前缀。如果有前缀相同的abcd,abca,abce,那么会非常方便,因为他们共用了同样的前缀,树的结构是一样的。
LeetCode上有一题是手写TrieTree的,这里就不说这些简单的东西了。
TrieTree是重要的,可以统计词频啊等等,kmp算法适用于单个匹配,而字典树就不一样了。可以多个匹配,于是有人发明了AC自动机,比较复杂的数据结构,是结合了KMP和TrieTree.赤裸裸的给你一堆string,让你统计某一个出现的次数。很直接了。这里不去延伸了。我只是想说一下敏感词过滤。
现将敏感词插入树种。接着,
很简单,三个指针。a,b指向string的位置,node指针指向root.
root是空的,不放东西的。
那么比如,我们将敏感词abc abe ae这三个放入树。
要检索的字符串为xxxabctttabxae
我们开始讲流程。
a,b指向x,index为0.root下面应该只有a,TrieNode[x]为空,说明他不是。我们用Stringbuilder sb来保存下来x
此时node都不动。
a,b向后移动。三个x都是这样的过程。
直到到了a,TrieNode[a]不为空,那么node移动去TrieNode[a]这个位置。
接着a停留,b开始向后走。直到b走到了t,此过程node一直在移动,到了t
没办法了,因为不是了,那么看boolean类型的变量。发现他是敏感词,将***或者什么也不加加入sb中,a = b。
当然如果不是结尾,就如上述例子ttt后面的ab,判断以后,发现不是,那么就将a~b加入sb,指针移动。
上面两个结束后不要忘记将node == root。

然后加了一些小的细节优化,比如防止特殊的字符插入,比如这样 a&& *b(@!c
要对这样的处理一下。
我在代码中展示。

public class TrieTree {
    public static class TrieNode {
        public TrieNode[] next;
        public int times;
        public int end; //以它为结尾,防止误判

        public TrieNode() {
            this.times = 0;
            this.end = 0;
            this.next = new TrieNode[1000000];
        }
    }
        public static class Trie {
            public static TrieNode root;

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

            public static void insert(String word) {
                if(word == null) return ;
                char[] ch = word.toCharArray();
                TrieNode node = root;
                int index = 0;
                for(int i = 0; i < ch.length; i ++) {
                    index = ch[i];
                    if(node.next[index] == null) {
                        node.next[index] = new TrieNode();
                    }
                    node = node.next[index];
                    node.times += 1;
                }
                node.end += 1;
            }

            public static void remove(String word) {
                if(search(word) == false) return ;
                char[] ch = word.toCharArray();
                int index = 0;
                TrieNode node = root;
                for(int i = 0; i < ch.length; i ++) {
                    index = ch[i];
                    if(node.next[index].times -- == 1) {
                        node.next[index] = null;
                        return ;
                    }
                    node = node.next[index];
                }
                node.end -= 1;
            }

            public static boolean search(String word) {
                if(word == null) return false;
                char[] ch = word.toCharArray();
                TrieNode node = root;
                int index = 0;
                for(int i = 0; i < ch.length; i ++) {
                    index = ch[i];
                    if(node.next[index] == null) {
                        return false;
                    }
                    node = node.next[index];
                }
                return node.end != 0;
            }

            public static int prefixNumber(String pre) { //统计前缀词频
                if(pre == null) return 0;
                char[] ch = pre.toCharArray();
                int index = 0;
                TrieNode node = root;
                for(int i = 0; i < ch.length; i ++) {
                    index = ch[i];
                    if(node.next[index] == null)
                        return 0;
                    node = node.next[index];
                }
                return node.times;
            }
            public String filter(String st) { // 敏感词过滤算法
                StringBuilder sb = new StringBuilder();
                st = moveSymbol(st);
                char[] ch = st.toCharArray();
                int a = 0, b = 0;
                TrieNode node = root;
                int index = 0;
                while (a < st.length() && b < st.length()) {
                    index = ch[a];
                    if(index < 0) continue;
                    if (root.next[index] == null) {
                        sb.append(ch[a]);
                        a += 1;
                        b += 1;
                    } else {
                        while (node.next[index] != null && b < ch.length) {
                            node = node.next[index];
                            b += 1;
                            if (b == ch.length) {
                                if (node.end == 0) {
                                    for (int i = a; i < b; i++)
                                        sb.append(ch[i]);
                                    return sb.toString();
                                } else {
                                    for (int i = a; i < b; i++)
                                        sb.append("*");
                                    return sb.toString();
                                }
                            }
                            index = ch[b] ;
                        }
                        if (node.end != 0) {
                            for (int i = a; i < b; i++)
                                sb.append("*");
                            a = b;
                            b = a;
                            node = root;
                        } else {
                            for (int i = a; i < b; i++)
                                sb.append(ch[i]);
                            a = b;
                            b = a;
                            node = root;
                        }
                    }
                }
                return sb.toString();
            }

            public boolean isSymbol(char c) {
                int isc = (int) c;
                return (isc < 0x2E80 || isc > 0x9FFF); // 东亚文字的范围
            }

            public String moveSymbol(String c) {
                StringBuilder sb = new StringBuilder();
                for(int i = 0; i < c.length(); i ++) {
                    if(!isSymbol(c.charAt(i))) {
                        sb.append(c.charAt(i));
                    }
                }
                return sb.toString();
            }
        }
}

你可能感兴趣的:(数据结构)