敏感词过滤-DFA算法
目錄
DFA算法簡介
Java實現(xiàn)DFA算法實現(xiàn)敏感詞過濾
測試方法
創(chuàng)建DFAMap
根據(jù)DFAMap進(jìn)行檢驗
完整代碼
與前綴樹的類似與不同
類似
不同
復(fù)雜度
DFA算法簡介
在實現(xiàn)文字過濾的算法中,DFA是唯一比較好的實現(xiàn)算法。DFA即Deterministic Finite Automaton,也就是確定有窮自動機,它是是通過event和當(dāng)前的state得到下一個state,即event+state=nextstate。下圖展示了其狀態(tài)的轉(zhuǎn)換
DFA,全稱 Deterministic Finite Automaton 即確定有窮自動機:從一個狀態(tài)通過一系列的事件轉(zhuǎn)換到另一個狀態(tài),即 state -> event -> state。
確定:狀態(tài)以及引起狀態(tài)轉(zhuǎn)換的事件都是可確定的,不存在“意外”。
有窮:狀態(tài)以及事件的數(shù)量都是可窮舉的。
我們可以將每個文本片段作為狀態(tài),例如“匹配關(guān)鍵詞”可拆分為“匹”、“匹配”、“匹配關(guān)”、“匹配關(guān)鍵”和“匹配關(guān)鍵詞”五個文本片段。
【過程】:
初始狀態(tài)為空,當(dāng)觸發(fā)事件“匹”時轉(zhuǎn)換到狀態(tài)“匹”;
觸發(fā)事件“配”,轉(zhuǎn)換到狀態(tài)“匹配”;
依次類推,直到轉(zhuǎn)換為最后一個狀態(tài)“匹配關(guān)鍵詞”。
再讓我們考慮多個關(guān)鍵詞的情況,例如“匹配算法”、“匹配關(guān)鍵詞”以及“信息抽取”。
?
?
在實現(xiàn)敏感詞過濾的算法中,我們必須要減少運算,而DFA在DFA算法中幾乎沒有什么計算,有的只是狀態(tài)的轉(zhuǎn)換。
Java實現(xiàn)DFA算法實現(xiàn)敏感詞過濾
測試方法
package algorithm.string.dfa;import java.util.HashSet; import java.util.List; import java.util.Set;public class Main {public static void main(String[] args) {Set<String> set=new HashSet<>();set.add("大中華");set.add("大中華帝國");set.add("大漢民族");set.add("日本人");set.add("日本鬼子");DFAUtil dfa=new DFAUtil();dfa.createDFAHashMap(set);Set<String> result=dfa.getSensitiveWordByDFAMap("大中華帝國和日本人",1);System.out.println("敏感詞有"+result.size()+"個");for(String string:result){System.out.println("違背敏感詞:"+string);}}}創(chuàng)建DFAMap
?????????在Java中實現(xiàn)敏感詞過濾的關(guān)鍵就是DFA算法的實現(xiàn)。首先我們對上圖進(jìn)行剖析。在這過程中我們認(rèn)為下面這種結(jié)構(gòu)會更加清晰明了。
?????????同時這里沒有狀態(tài)轉(zhuǎn)換,沒有動作,有的只是Query(查找)。我們可以認(rèn)為,通過S query U、V,通過U query V、P,通過V query U P。通過這樣的轉(zhuǎn)變我們可以將狀態(tài)的轉(zhuǎn)換轉(zhuǎn)變?yōu)槭褂肑ava集合的查找。
誠然,加入在我們的敏感詞庫中存在如下幾個敏感詞:日本人、日本鬼子、毛.澤.東。那么我需要構(gòu)建成一個什么樣的結(jié)構(gòu)呢?
首先:query 日 ---> {本}、query 本 --->{人、鬼子}、query 人 --->{null}、query 鬼 ---> {子}。形如下結(jié)構(gòu):
?????????下面我們在對這圖進(jìn)行擴展:
?????????這樣我們就將我們的敏感詞庫構(gòu)建成了一個類似與一顆一顆的樹,這樣我們判斷一個詞是否為敏感詞時就大大減少了檢索的匹配范圍。比如我們要判斷日本人,根據(jù)第一個字我們就可以確認(rèn)需要檢索的是那棵樹,然后再在這棵樹中進(jìn)行檢索。
?????????但是如何來判斷一個敏感詞已經(jīng)結(jié)束了呢?利用標(biāo)識位來判斷。
?????????所以對于這個關(guān)鍵是如何來構(gòu)建一棵棵這樣的敏感詞樹。下面我已Java中的HashMap為例來實現(xiàn)DFA算法。具體過程如下:
日本人,日本鬼子為例
?????????1、在hashMap中查詢“日”看其是否在hashMap中存在,如果不存在,則證明已“日”開頭的敏感詞還不存在,則我們直接構(gòu)建這樣的一棵樹。跳至3。
?????????2、如果在hashMap中查找到了,表明存在以“日”開頭的敏感詞,設(shè)置hashMap = hashMap.get("日"),跳至1,依次匹配“本”、“人”。
?????????3、判斷該字是否為該詞中的最后一個字。若是表示敏感詞結(jié)束,設(shè)置標(biāo)志位isEnd = 1,否則設(shè)置標(biāo)志位isEnd = 0;
程序?qū)崿F(xiàn)如下:
注意:本文的編寫和代碼的編寫參考了https://blog.csdn.net/chenssy/article/details/26961957
但是在代碼上對它進(jìn)行了改進(jìn),修復(fù)了幾個bug
/*{日=* {本=* {人={isEnd=1}, * 鬼=* {子={isEnd=1}, * isEnd=0}, * isEnd=0}, * isEnd=0}, * * 大=* {漢=* {民={isEnd=0, * 族={isEnd=1}}, * isEnd=0}, * isEnd=0, * 中={isEnd=0, * 華={isEnd=1, * 帝={isEnd=0, * 國={isEnd=1}}}}}}*//**set作為敏感詞,創(chuàng)建出對應(yīng)的dfa的Map,以供檢驗敏感詞* @param set*/public void createDFAHashMap(Set<String> set){HashMap<String, Object> nowMap;//根據(jù)set的大小,創(chuàng)建map的大小dfaMap=new HashMap<>(set.size());//對set里的字符串進(jìn)行循環(huán)for(String key:set){//對每個字符串最初,nowMap就是dfaMapnowMap=dfaMap; for(int i=0;i<key.length();i++){//一個個字符循環(huán)String nowChar=String.valueOf(key.charAt(i));//根據(jù)nowChar得到nowMap里面對應(yīng)的valueHashMap<String, Object> map=(HashMap<String, Object>)nowMap.get(nowChar);//如果map為空,則說明nowMap里面沒有以nowChar開頭的東西,則創(chuàng)建一個新的hashmap,//以nowChar為key,新的map為value,放入nowMapif(map==null){map=new HashMap<String,Object>();nowMap.put(nowChar, map);} //nowMap=map,就是nowChar對應(yīng)的對象nowMap=map;//最后在nowMap里設(shè)置isEnd//如果nowMap里面已經(jīng)有isEnd,并且為1,說明以前已經(jīng)有關(guān)鍵字了,就不再設(shè)置isEnd//因為如果沒有這一步,大中華和大中華帝國,先設(shè)置大中華//在大中華帝國設(shè)置的時候,華對應(yīng)的map有isEnd=1,如果這時對它覆蓋,就會isEnd=0,導(dǎo)致大中華這個關(guān)鍵字失效if(nowMap.containsKey("isEnd")&&nowMap.get("isEnd").equals("1")){continue;} if(i!=key.length()-1){nowMap.put("isEnd", "0");}else{nowMap.put("isEnd", "1");} } }System.out.println(dfaMap);}得到的結(jié)果是
{日={本={人={isEnd=1}, 鬼={子={isEnd=1}, isEnd=0}, isEnd=0}, isEnd=0}, 大={漢={民={isEnd=0, 族={isEnd=1}}, isEnd=0}, isEnd=0, 中={isEnd=0, 華={isEnd=1, 帝={isEnd=0, 國={isEnd=1}}}}}}
具體可以看上面的注釋
根據(jù)DFAMap進(jìn)行檢驗
?? 敏感詞庫我們一個簡單的方法給實現(xiàn)了,那么如何實現(xiàn)檢索呢?檢索過程無非就是hashMap的get實現(xiàn),找到就證明該詞為敏感詞,否則不為敏感詞。過程如下:假如我們匹配“中國人民萬歲”。
1、第一個字“中”,我們在hashMap中可以找到。得到一個新的map = hashMap.get("")。
2、如果map == null,則不是敏感詞。否則跳至3
?3、獲取map中的isEnd,通過isEnd是否等于1來判斷該詞是否為最后一個。如果isEnd == 1表示該詞為敏感詞,更新resultLength否則跳至1。
?通過這個步驟我們可以判斷“中國人民”為敏感詞,但是如果我們輸入“中國女人”則不是敏感詞了。
?
這里的改進(jìn)是
取消了i的直接增加
//這個對應(yīng)的是一個敏感詞內(nèi)部的關(guān)鍵字(不包括首部),如果加上,大中華帝國,對應(yīng)大中華和中華兩個敏感詞,只會對應(yīng)大中華而不是兩個//i=i+length-1;//減1的原因,是因為for會自增加入了resultLength
//最終匹配敏感詞的長度,因為匹配規(guī)則2,如果大中華帝,對應(yīng)大中華,大中華帝國,在華的時候,nowLength=3,因為是最后一個字,將nowLenth賦給resultLength//然后在帝的時候,now=4,result=3,然后不匹配,resultLength就是上一次最大匹配的敏感詞的長度int resultLength=0;這里面字符串為 大中華帝國和日本人
如果最小匹配,對應(yīng)
敏感詞有2個
違背敏感詞:大中華
違背敏感詞:日本人
最大匹配,對應(yīng)
敏感詞有2個
違背敏感詞:大中華帝國
違背敏感詞:日本人
完整代碼
package algorithm.string.dfa;import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set;public class DFAUtil {HashMap<String, Object> dfaMap;public static final int minMatchType=1;public static final int maxMatchType=2;/*{日=* {本=* {人={isEnd=1}, * 鬼=* {子={isEnd=1}, * isEnd=0}, * isEnd=0}, * isEnd=0}, * * 大=* {漢=* {民={isEnd=0, * 族={isEnd=1}}, * isEnd=0}, * isEnd=0, * 中={isEnd=0, * 華={isEnd=1, * 帝={isEnd=0, * 國={isEnd=1}}}}}}*/ /**set作為敏感詞,創(chuàng)建出對應(yīng)的dfa的Map,以供檢驗敏感詞* @param set*/public void createDFAHashMap(Set<String> set){HashMap<String, Object> nowMap;//根據(jù)set的大小,創(chuàng)建map的大小dfaMap=new HashMap<>(set.size());//對set里的字符串進(jìn)行循環(huán)for(String key:set){//對每個字符串最初,nowMap就是dfaMapnowMap=dfaMap; for(int i=0;i<key.length();i++){//一個個字符循環(huán)String nowChar=String.valueOf(key.charAt(i));//根據(jù)nowChar得到nowMap里面對應(yīng)的valueHashMap<String, Object> map=(HashMap<String, Object>)nowMap.get(nowChar);//如果map為空,則說明nowMap里面沒有以nowChar開頭的東西,則創(chuàng)建一個新的hashmap,//以nowChar為key,新的map為value,放入nowMapif(map==null){map=new HashMap<String,Object>();nowMap.put(nowChar, map);} //nowMap=map,就是nowChar對應(yīng)的對象nowMap=map;//最后在nowMap里設(shè)置isEnd//如果nowMap里面已經(jīng)有isEnd,并且為1,說明以前已經(jīng)有關(guān)鍵字了,就不再設(shè)置isEnd//因為如果沒有這一步,大中華和大中華帝國,先設(shè)置大中華//在大中華帝國設(shè)置的時候,華對應(yīng)的map有isEnd=1,如果這時對它覆蓋,就會isEnd=0,導(dǎo)致大中華這個關(guān)鍵字失效if(nowMap.containsKey("isEnd")&&nowMap.get("isEnd").equals("1")){continue;} if(i!=key.length()-1){nowMap.put("isEnd", "0");}else{nowMap.put("isEnd", "1");} } }System.out.println(dfaMap);}/** 用創(chuàng)建的dfaMap,根據(jù)matchType檢驗字符串string是否包含敏感詞,返回包含所有對于敏感詞的set* @param string 要檢查是否有敏感詞在內(nèi)的字符串* @param matchType 檢查類型,如大中華帝國牛逼對應(yīng)大中華和大中華帝國兩個關(guān)鍵字,1為最小檢查,會檢查出大中華,2位最大,會檢查出大中華帝國 * @return*/public Set<String> getSensitiveWordByDFAMap(String string,int matchType){Set<String> set=new HashSet<>();for(int i=0;i<string.length();i++){//matchType是針對同一個begin的后面,在同一個begin匹配最長的還是最短的敏感詞int length=getSensitiveLengthByDFAMap(string,i,matchType);if(length>0){set.add(string.substring(i,i+length));//這個對應(yīng)的是一個敏感詞內(nèi)部的關(guān)鍵字(不包括首部),如果加上,大中華帝國,對應(yīng)大中華和中華兩個敏感詞,只會對應(yīng)大中華而不是兩個//i=i+length-1;//減1的原因,是因為for會自增}} return set;}/**如果存在,則返回敏感詞字符的長度,不存在返回0* @param string* @param beginIndex* @param matchType 1:最小匹配規(guī)則,2:最大匹配規(guī)則* @return*/public int getSensitiveLengthByDFAMap(String string,int beginIndex,int matchType){//當(dāng)前匹配的長度int nowLength=0;//最終匹配敏感詞的長度,因為匹配規(guī)則2,如果大中華帝,對應(yīng)大中華,大中華帝國,在華的時候,nowLength=3,因為是最后一個字,將nowLenth賦給resultLength//然后在帝的時候,now=4,result=3,然后不匹配,resultLength就是上一次最大匹配的敏感詞的長度int resultLength=0;HashMap<String, Object> nowMap=dfaMap;for(int i=beginIndex;i<string.length();i++){String nowChar=String.valueOf(string.charAt(i));//根據(jù)nowChar得到對應(yīng)的map,并賦值給nowMapnowMap=(HashMap<String, Object>)nowMap.get(nowChar);//nowMap里面沒有這個char,說明不匹配,直接返回if(nowMap==null){break;}else{nowLength++;//如果現(xiàn)在是最后一個,更新resultLengthif("1".equals(nowMap.get("isEnd"))){resultLength=nowLength;//如果匹配模式是最小,直接匹配到,退出//匹配模式是最大,則繼續(xù)匹配,resultLength保留上一次匹配到的lengthif(matchType==minMatchType){break;}}}} return resultLength;} }與前綴樹的類似與不同
前綴樹總結(jié)
前綴樹(字典樹)總結(jié)-java版_xushiyu1996818的博客-CSDN博客_前綴樹java
類似
思路類似,都是一個個節(jié)點,每個節(jié)點包含通往下一群節(jié)點的容器。
不同
前綴樹:
這種java實現(xiàn)的下一個節(jié)點的容器是new TreeNode[26],同時還有path(經(jīng)過這個節(jié)點的數(shù)量),end(以這個節(jié)點結(jié)尾的數(shù)量),傾向于查找字符串的個數(shù)和前綴的個數(shù)
dfa:
java實現(xiàn)的容器是Map<String,Object>,沒有path,只有key=isEnd,傾向于查詢這個字符串中是否包含敏感詞
其實兩者本來是一個源頭,只是為了不同的目的,從而node的構(gòu)造不同
復(fù)雜度
n個敏感詞,敏感詞的平均長度是k,被查詢字符串長度O(m)
時間復(fù)雜度? 創(chuàng)建dfaMap,O(nk),,查詢是否有敏感詞,O(mk)
空間復(fù)雜度? O(nk)
總結(jié)
以上是生活随笔為你收集整理的敏感词过滤-DFA算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 读《鸟哥的linux私房菜》有感--第四
- 下一篇: SubSonic中的字段付值--Make