敏感詞過濾在社群發帖、網站檢索、簡訊傳送等場景下是很常見的需求,尤其是在高併發場景下如何實現敏感詞過濾,都對過濾演算法提出了更高的效能要求,Ahocorasick演算法能夠實現毫秒級的萬字過濾匹配,能夠很好的滿足各種場景下的敏感詞過濾需求。
Aho-Corasick演算法透過將模式串預處理為確定有限狀態自動機,對待匹配文字掃描一遍就能完成匹配。演算法複雜度為O(n),即與模式串的數量和長度無關。AC自動機是多模式匹配的一個經典資料結構,原理是和KMP一樣的構造Fail指標,不過AC自動機是在Trie樹上構造的,但原理是一樣的。
多模式匹配:
多模式匹配就是有多個模式串P1,P2,P3…,Pm
,求出所有這些模式串在連續文字T1…n中的所有可能出現的位置。
例如:求出模式集合 {“nihao”,“hao”,“hs”,“hsr”}
在給定文字 sdmfhsgnshejfgnihaofhsrnihao
中所有可能出現的位置。
想要了解Aho-Corasick演算法,就首先要從字典樹與DFA開始說起:
字典樹(Trie)
https://www.cnblogs.com/vipsoft/p/17722820.html
字典樹(Trie)是一種很特別的樹狀資訊檢索資料結構。利用字串的公共字首(common-prefix)來減少查詢時間,搜尋時間為O(d),d為樹的深度。
字典樹的原則:
- 根節點不含字元
- 根節點到某一終點連起來即為搜尋字串
- 任意節點的所有子節點包含字元不同
AC 演算法思想
AC演算法的主要思想就是構造的有限狀態自動機,根據有限狀態自動機會根據輸入進行模式串匹配。有限狀態自動機會隨著字元的輸入而發生狀態轉移,轉移的狀態有如下三種:
- success 狀態,即AC自動機根據輸入有能直接到達的狀態;
- failure 狀態,即AC自動機根據輸入沒有直接到達的狀態,這時候就會發生跳轉,跳轉到其他一個路徑;
- output 狀態,即成功匹配到一個輸入段;
示例講解
以經典的ushers為例,模式串是he/ she/ his /hers
構建字典樹,如圖:(紅色表示接受態)
文字為ushers
, 構建的自動機如圖(帶虛線)
自動機從根節點0
出發,首先嚐試按success錶轉移
按照文字的指示轉移,也就是接收一個u
, 此時success表中並沒有相應路線,轉移失敗,失敗了則按照failure表回去。
按照文字指示,這次接收一個s
,轉移到狀態3
,成功了繼續按success錶轉移,h
到狀態4
,e
到狀態5
r
失敗由5
跳轉步驟2
,或者遇到output表中標明的“可輸出狀態”(she
)。此時輸出匹配到的模式串,然後將此狀態視作普通的狀態繼續轉移。
演算法高效之處在於,當自動機接受了“ushe”之後,再接受一個r會導致無法按照success錶轉移,此時自動機會聰明地按照failure錶轉移到2號狀態,並經過幾次轉移後輸出“hers”。來到2號狀態的路不止一條,從根節點一路往下,“h→e”也可以到達。而這個“he”恰好是“ushe”的結尾,狀態機就彷彿是壓根就沒失敗過,也沒有接受過中間的字元“us”,直接就從初始狀態按照“he”的路徑走過來一樣。
功能解析
用 5 個模式串:"she","he","say","shr","her"
所建的字典樹
透過分析可知,"she" 具有字尾字串 "he","her" 具有字首字串,因此當 "she" 模式串發生失配的時候,就可以透過失配指標繼續匹配 "her" 模式串,那麼就需要將 "she" 中的 "h" 結點的失配指標指向 "her" 的 "h" 結點,將 "she" 中的 "e" 結點的失配指標指向 "her" 的 "e" 結點。至於其他的結點,由於不存在共有的字首字串和字尾字串,因此它們的失配指標指向根結點。因此對於如圖字典樹,失配指標的關係如圖所示:
透過分析可以得知,進行跳轉的另一個模式串的結點深度一定小於跳轉之前的結點的深度,這是因為若跳轉後的結點深度大於原結點的深度,就無法保證跳轉後模式串的字首字串與進行跳轉的模式串的字尾字串相匹配,這樣結點數量完全不夠。
例如上文的例子中,透過失配指標聯絡的 "she" 中的 "h" 結點和 "her" 的 "h" 結點(藍色標出)中前者的層數大於後者, "she" 中的 "e" 結點和 "her" 的 "e" 結點(紫色標出)中前者的層數也大於後者:
根據這個特點,我們可以透過訪問當前結點的雙親結點的方式進行試探,對於某一個字母結點(原字母),透過對其雙親的失配指標的訪問,尋找到其他的結點,這個結點滿足其孩子結點中存在與原字母相同的結點,此時就把原字母結點的失配指標指向尋找到的結點中與原字母相同的孩子結點。若訪問到了根結點,沒有發現符合要求的結點,則失配指標指向根結點。
程式碼示例(Python)
ahocorasick 目前改名為 pyahocorasick
https://pypi.tuna.tsinghua.edu.cn/simple/pyahocorasick/
#安裝 ahocorasick 庫
pip install pyahocorasick==1.4.4 -i https://pypi.tuna.tsinghua.edu.cn/simple
# coding:utf-8
import ahocorasick
def make_AC(AC, word_set):
for word in word_set:
AC.add_word(word, word) # 向trie樹中新增單詞
return AC
def ac_demo():
'''
ahocosick:自動機的意思
可實現自動批次匹配字串的作用,即可一次返回該條字串中命中的所有關鍵詞
'''
key_list = ["脹痛", "看東西有時候清楚有時候不清楚", "畏光"]
AC_KEY = ahocorasick.Automaton()
AC_KEY = make_AC(AC_KEY, set(key_list))
AC_KEY.make_automaton()
test_str_list = ["請問最近看東西有時候清楚有時候不清楚是怎麼回事", "有時候眼睛脹痛,畏光"]
for content in test_str_list:
name_list = set()
for item in AC_KEY.iter(content): # 將AC_KEY中的每一項與content內容作對比,若匹配則返回
name_list.add(item[1])
name_list = list(name_list)
if len(name_list) > 0:
print("\n", content, "--->命中的關鍵詞有:", "、".join(name_list))
if __name__ == "__main__":
ac_demo()
請問最近看東西有時候清楚有時候不清楚是怎麼回事 --->命中的關鍵詞有: 看東西有時候清楚有時候不清楚
有時候眼睛脹痛,畏光 --->命中的關鍵詞有: 畏光、脹痛