對於乙個遊戲,如果有聊天功能,那麼我們就會希望我們的聊天系統能夠對玩家的輸入進行判斷,如果玩家的輸入中含有一些敏感詞彙,那麼我們就禁止玩家傳送聊天,或者把敏感詞轉換為 * 來替換。
設我們已經有了乙個敏感詞詞庫(從相關部門獲取到的,或者網上找來的),那麼我們最容易想到的過濾敏感詞的方法就是:
遍歷整個敏感詞庫,拿到敏感詞,再判斷玩家輸入的字串中是否有該敏感詞,如果有就把敏感詞字元替換為 *
但這樣的方法,我們需要遍歷整個敏感詞庫,並且對玩家輸入的字串進行替換。而整個敏感詞庫中一般會有上千個字串。而玩家聊天輸入的字串一般也就 20~30 個字元。
因此,這種方法的效率是非常低的,無法應用到真實的開發中。
而使用 dfa 演算法就可以實現高效的敏感詞過濾。使用 dfa 演算法,我們只需要遍歷一遍玩家輸入的字串即可將所有存在的敏感詞進行替換。
dfa 演算法是通過提前構造出乙個 樹狀查詢結構(實際上應該說是乙個 森林),之後根據輸入在該樹狀結構中就可以進行非常高效的查詢。
設我們有乙個敏感詞庫,詞酷中的詞彙為:
我愛你我愛他
我愛她我愛你呀
我愛他呀
我愛她呀
我愛她啊
那麼就可以構造出這樣的樹狀結構:
設玩家輸入的字串為:白菊我愛你呀哈哈哈
我們遍歷玩家輸入的字串 str,並設指標 i 指向樹狀結構的根節點,即最左邊的空白節點:
str[0] = 『白』 時,此時 tree[i] 沒有指向值為 『白』 的節點,所以不滿足匹配條件,繼續往下遍歷
str[1] = 『菊』,同樣不滿足匹配條件,繼續遍歷
str[2] = 『我』,此時 tree[i] 有一條路徑連線著 『我』 這個節點,滿足匹配條件,i 指向 『我』 這個節點,然後繼續遍歷
str[3] = 『愛』,此時 tree[i] 有一條路徑連著 『愛』 這個節點,滿足匹配條件,i 指向 『愛』,繼續遍歷
str[4] = 『你』,同樣有路徑,i 指向 『你』,繼續遍歷
str[5] = 『呀』,同樣有路徑,i 指向 『呀』
此時,我們的指標 i 已經指向了樹狀結構的末尾,即此時已經完成了一次敏感詞判斷。我們可以用變數來記錄下這次敏感詞匹配開始時玩家輸入字串的下標,和匹配結束時的下標,然後再遍歷一次將字元替換為 * 即可。
結束一次匹配後,我們把指標 i 重新指向樹狀結構的根節點處。
此時我們玩家輸入的字串還沒有遍歷到頭,所以繼續遍歷:
str[6] = 『哈』,不滿足匹配條件,繼續遍歷
str[7] = 『哈』 …
str[8] = 『哈』 …
可以看出我們遍歷了一次玩家輸入的字串,就找到了其中的敏感詞彙。
而在這一段標題的下面,我說 dfa 演算法一開始構造的結構實際上算是一種森林,因為對於乙個更完整的敏感詞庫而言,其構造出來的結構是這樣的:
如果不看該結構的根節點,即那個空白節點,那麼就可以看作是由乙個個樹結構組成的森林。
理解了 dfa 演算法是如何匹配過濾詞的,接下來我們開始從**層面來**如何根據敏感詞庫構造出這樣的森林結構。
不論是樹,還是森林,都是由乙個個節點構成的,因此我們來**該結構中的節點應該儲存哪些資訊。
按照正常的樹結構來說,節點結束儲存自身的值,和 與其連線的子節點的指標。
但對於 dfa 演算法的結構,子節點的數量一開始我們是不確定的。所以,我們可以用乙個 list 來儲存 所以子節點的指標,但是這樣子的話,我們在匹配時進行查詢路徑就需要遍歷整個 list,這樣子效率是比較慢的。
為了達到 o(1) 的查詢效率,我們可以使用雜湊表來儲存子節點的指標。
我們還可以直接用 雜湊表來作為森林的入口節點:
該雜湊表中存放著 一系列 key 為 不同的敏感詞開頭字元 value 為 表示該字元的節點 的鍵值對
並且因為雜湊表可以存放不同型別物件的特點(只要繼承自 object),我們還可以存放可乙個 key 為 『isend』 value 為 0 的鍵值對。 value 為 0 表示當前節點不為結構的末尾, value 為 1 表示當前節點為結構的末尾。
那麼對於結構中的其它節點,同樣可以用雜湊表來構造。 對於該節點表示的字元,我們在其父節點中包含的鍵值對中已經儲存了(因為我們的結構最終有乙個空白根節點,其裡面的鍵值對,key 儲存了敏感詞彙的開頭字元, value 就又是乙個雜湊表 即其子節點)
並且每個節點,也就是雜湊表,裡面也儲存乙個 kye 為 「isend」 value 為 0/1 的鍵值對。 然後也儲存了一系列的 key 為其子節點表示的字元, value 為其子節點(雜湊表) 的鍵值對。
我們再來舉個具體例子表述:
設有這樣的結構:
該結構最開始就是其空白根節點,即雜湊表,我們設其為 map
那麼,對於 「我愛你呀」 這個敏感詞,其查詢過程就為:
map[『我』][『愛』][『你』][『呀』][『isend』] == 1
經過以上分析,我們就可以得出大概地**構造該結構的過程:
1、建立乙個雜湊表,作為該結構的空白根節點
2、遍歷敏感詞詞庫,得到乙個敏感詞字串
3、遍歷敏感詞字串,得到乙個當前遍歷字元
4、在樹結構中查詢是否已經包含了當前遍歷字元,如果包含則直接走到樹結構中已經存在的這個節點,然後繼續向下遍歷字元。
查詢過程為:
對於敏感詞的第乙個字串而言:
indexmap = map // 相當於指向樹結構節點的指標
if(indexmap.containskey(『c』)) indexmap = indexmap[『c』]
這樣,我們的 indexmap 相當於乙個指標,就指向了樹結構中已經存在了的相同節點
對於後面的字元也是同樣的:
if(indexmap.containskye(『c』)) indexmap = indexmap[『c』]
如果樹結構中不存在,或者是當前指標指向的節點,其所有子節點都沒有表示當遍歷到的字元,則我們就需要建立乙個子節點,即新增乙個鍵值對,其 key 為當前遍歷到的字元, value 為新建乙個雜湊表。
5、判斷當前遍歷的字元,是否是當前字串的最後乙個。如果是 則新增鍵值對 key 為 「isend」 value 為 1。 如果不是,則新增鍵值對 key 為 「isend」 value 為 0。
對於 dfa 演算法的結構構造論述到此完畢,接下來給出構造**(使用 c# 實現)。
private
hashtable map;
private
void
initfilter
(list<
string
> words)
else
if(j == word.length -1)}}}
dfa 演算法查詢過程的原理在上面其實已經討論過,也舉了例子,並且查詢過程其實與初始化構造結構的過程有些相似之處。所以這裡不做贅述,直接給出**。
private
intcheckfilterword
(string txt,
int beginindex)
else
break;}
if(!flag) len =0;
return len;
}public
string
serachfilterwordandreplace
(string txt)
i += len;
}else
++i;
}return sb.
tostring()
;}
演算法 DFA敏感詞過濾
最近剛好有群友問到關於敏感詞過濾的問題,當時有人給出了一些辦法。1.利用hashset,對傳多來的字串進行比較。或者將敏感詞儲存到資料庫或者其他地方,然後和傳入的詞做匹配。2.正規表示式匹配。上述兩個方法不用想肯定都是很慢的。後來有人說道可以利用dfa演算法,因此我去研究了一下,增加自己的知識面。具...
前端 DFA 敏感詞過濾
最近在做遊戲的聊天功能,需要在客戶端接入敏感詞過濾,較低成本的實現方法有字典匹配和正規表示式匹配,但效率上較低。大致 google 了一遍,發現dfa演算法是實現敏感詞過濾效率較高的選擇,下面是具體實現過程。dfa演算法,即deterministic finite automaton,中文翻譯是有窮...
Java實現DFA演算法實現敏感詞過濾
在實現文字過濾的演算法中,dfa是唯一比較好的實現演算法。dfa即deterministic finite automaton,也就是確定有窮自動機,它是是通過event和當前的state得到下乙個state,即event state nextstate。下圖展示了其狀態的轉換 在這幅圖中大寫字母 ...