替換表單中的鍵值對 遊戲中敏感詞的過濾之DFA演算法

2021-10-16 06:50:42 字數 3738 閱讀 1962

對於乙個遊戲,如果有聊天功能,那麼我們就會希望我們的聊天系統能夠對玩家的輸入進行判斷,如果玩家的輸入中含有一些敏感詞彙,那麼我們就禁止玩家傳送聊天,或者把敏感詞轉換為 * 來替換。

設我們已經有了乙個敏感詞詞庫(從相關部門獲取到的,或者網上找來的),那麼我們最容易想到的過濾敏感詞的方法就是:

遍歷整個敏感詞庫,拿到敏感詞,再判斷玩家輸入的字串中是否有該敏感詞,如果有就把敏感詞字元替換為 *

但這樣的方法,我們需要遍歷整個敏感詞庫,並且對玩家輸入的字串進行替換。而整個敏感詞庫中一般會有上千個字串。而玩家聊天輸入的字串一般也就 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(listwords)

else

if (j == word.length - 1)}}

}

dfa 演算法查詢過程的原理在上面其實已經討論過,也舉了例子,並且查詢過程其實與初始化構造結構的過程有些相似之處。所以這裡不做贅述,直接給出**。

private int checkfilterword(string txt, int beginindex)

else break;

}if (!flag) len = 0;

return len;

}public string serachfilterwordandreplace(string txt)

i += len;

}else ++i;

}return sb.tostring();

}

Python遍歷字典中的鍵值對

python為字典型別提供了items 方法,items 方法會將字典裡的所有的鍵與值一起返回。例如,餐館有乙個選單包含了菜名和 資訊。菜名和 顧客都需要知道,可以通過遍歷輸出menu字典的鍵和值來實現。coding utf 8 建立並初始化menu選單字典 menu 利用items 方法遍歷輸出鍵...

實踐中遇到的坑 對map中的鍵值對根據值排序

map不提供sort功能,sort演算法對map容器也不適用,這個時候,我們可以建立乙個vector來暫存鍵值對,排序完畢後,再返回即可 typedef pairpii vectorfinalresult multimaptranscript 假設內部已有資料 先將內容傳入 for auto itr...

陣列中的鍵值對去重 陣列去重

陣列去重 將陣列中重複的元素找出來並刪減為乙個。目的很簡單,方法倒有很多,以至於我費盡心思去考慮各種方法的好處壞處並進行對比,最終得出在各種場合適用的不同方法。希望有不同意見的儘管指出來。方法1 function unique1 array 原理 新定義乙個陣列,結合乙個物件輔助 時間複雜度 o n...