1. 程式人生 > >DFA確定性有限狀態機過濾敏感詞

DFA確定性有限狀態機過濾敏感詞

介紹

通常把確定的有窮狀態自動機(有窮狀態自動機也就是本文討論的這種狀態機)稱為DFA,把非確定的有窮狀態自動機稱為NFA。

原理

狀態機就是通過當前狀態state和事件event得到下一個狀態state,即state+event=nextstate

DFA確定性有限狀態機是有限型別的事件和狀態切換。

舉個例子,存在4個狀態,事件2個。

狀態切換圖吐下:


事件  

a       b

狀態切換
S        U       V
U        Q       V
V        U       Q
Q        Q       Q

應用

網路過濾敏感詞的常用演算法。

在編譯程式時, 詞法分析階段將原始碼中的

語法符號變成語法的集合就是通過確定有限自動機實現的。       

過濾敏感詞

在文字過濾系統中,為了能夠應付較高的併發,需要儘量減少計算。採用DFA演算法基本沒有什麼計算,基本是狀態轉移。

本例中採用多叉樹實現的有限狀態機。每個utf8編碼的中文字一般是3個字元,英文字母是一個字元。關鍵詞表則是有限狀態機的所有的狀態。輸入的句子被分成一個個字元,每個字元是一個ascii碼,範圍在0~255之間,以其為索引查詢子樹中的成員。每個子樹包含256個數組成員,即最多是256個子節點,沒有子節點的則為空。

這裡的實現查詢關鍵字不再重複查詢已查找出來的關鍵字的的部分內容作為起點的詞。

程式碼如下:

//dfa的多叉樹
#define DFA_TREE_NODE_LEN (256)
/**
 * 樹節點
 * 每個節點包含一個長度為256的陣列
 */
struct TreeNode
{
    bool end;//關鍵詞的終結
    std::vector<TreeNode*> subNodes;
    TreeNode()
    {
        end = false;
    }
    ~TreeNode()
    {
        for(oss::uint16 i = 0;i < subNodes.size();++i)
        {
            delete subNodes[i];
        }
        subNodes.clear();
    }
    /**
     * 向指定位置新增節點樹
     * @param index
     * @param node
     */
    bool setSubNode(oss::uint16 index,TreeNode* node)
    {
        if(subNodes.empty())
        {
            subNodes.resize(DFA_TREE_NODE_LEN,NULL);
        }
        if (index >= subNodes.size())
        {
            return false;
        }
        subNodes[index] = node;
        return true;
    }
    bool getSubNode(oss::uint16 index,TreeNode*& node)const
    {
        if (index >= subNodes.size())
        {
            return false;
        }
        if(!subNodes[index])
        {
            return false;
        }
        node = subNodes[index];
        return true;
    }
    bool isKeywordEnd()const
    {
        return end;
    }
    void setKeywordEnd(bool end = true)
    {
        this->end = end;
    }
};


class DFA
{
public:
    DFA()
    {
        rootNode = new TreeNode();
    }
    ~DFA()
    {
        delete rootNode;
    }
    /**
     * 建立DFA
     * @param keywordList
     */
    bool createKeywordTree(const std::vector<std::string> &keywordList)
    {
        //根據關鍵詞設定節點數
        for (std::vector<std::string>::const_iterator it = keywordList.begin();
                        it != keywordList.end();++it)
        {
            const std::string& keyword = *it;
            if(keyword.empty())
            {
                continue;
            }
            int keyLen = keyword.length();
            const char* bytes = keyword.data();//轉換成ASCII字元
            TreeNode* tempNode = rootNode;
            //迴圈每個位元組
            for (int i = 0; i < keyLen; i++)
            {
                int index = bytes[i] & 0xff; //字元轉換成數字,作為索引來設定節點(ASCII字元的值是0~255)
                TreeNode* node(NULL);
                if(!tempNode->getSubNode(index,node))//沒有該值的就設定
                {
                    node = new TreeNode();
                    if(!tempNode->setSubNode(index, node))
                    {
                        printf("setSubNode failed:{%s}\n", keyword.c_str());
                        return false;
                    }
                }
                tempNode = node;
                if(i == keyLen - 1)//關鍵詞結束, 設定節點樹結束標誌
                {
                    tempNode->setKeywordEnd();
//                    printf("DFA:{%s}len(%d)", keyword.c_str(),keyword.length());
                }
            }
        }
        return true;
    }
    /**
     * 搜尋關鍵字
     */
    bool searchKeyword(const std::string& tofilter,std::vector<std::string>& words)
    {
        words.clear();
        if(tofilter.empty())
        {
            return true;
        }
        bool boFound(false);
        int len = tofilter.length();
        const char* bytes = tofilter.data();//轉換成ASCII字元
        TreeNode* tempNode = rootNode;
        int rollback = 0;   //回滾數
        int position = 0;//當前比較的位置
        while (position < len)
        {
            char c = bytes[position];
            int index = c & 0xFF;
            keywordBuffer.push_back(c); //寫關鍵詞快取
            if(!tempNode->getSubNode(index,tempNode))//當前位置的匹配結束
            {
                position = position - rollback; //回退 並測試下一個位元組
                rollback = 0;//不是任何關鍵字 回退步數 置為0
                tempNode = rootNode;//狀態機復位
                keywordBuffer.clear();//清空
            }
            else if(tempNode->isKeywordEnd())//是結束點 記錄關鍵詞
            {
//                printf("Find key:{%s}", keywordBuffer.c_str());
                words.push_back(keywordBuffer);
				keywordBuffer.clear();
                rollback = 0;   //遇到結束點 回退步數 置為0(這裡不檢查關鍵字內開始的可能的另一個關鍵字)
                boFound = true;
            }
            else
            {
                rollback++; //非結束點 回退步數加1
            }
            position++;
        }
        return boFound;
    }

    bool searchKeyword(const std::string& tofilter,std::string& filtered)
    {
        filtered = tofilter;
        if(tofilter.empty())
        {
            return true;
        }
        bool boFound(false);
        int len = tofilter.length();
        const char* bytes = tofilter.data();//轉換成ASCII字元
        TreeNode* tempNode = rootNode;
        int rollback = 0;   //回滾數
        int position = 0;//當前比較的位置
        while (position < len)
        {
            char c = bytes[position];
            int index = c & 0xFF;
            keywordBuffer.push_back(c); //寫關鍵詞快取
            if(!tempNode->getSubNode(index,tempNode))//當前位置的匹配結束
            {
                position = position - rollback; //回退 並測試下一個位元組
                rollback = 0;//不是任何關鍵字 回退步數 置為0
                tempNode = rootNode;//狀態機復位
                keywordBuffer.clear();//清空
            }
            else if(tempNode->isKeywordEnd())//是結束點 記錄關鍵詞
            {
//                printf("Find key:{%s},position(%d),size(%u),filtered size(%u)",
//                                keywordBuffer.c_str(),
//                                position,keywordBuffer.size(),filtered.size());
                change(filtered,position - keywordBuffer.size() + 1,keywordBuffer.size());
				keywordBuffer.clear();
                rollback = 0;   //遇到結束點 回退步數 置為0(這裡不檢查關鍵字內開始的可能的另一個關鍵字)
                boFound = true;
            }
            else
            {
                rollback++; //非結束點 回退步數加1
            }
            position++;
        }
        return boFound;
    }
private:
    void change(std::string& filtered,int index,int size,char letter = '*')
    {
        int i = index >= 0 ?index :0;
        int l = filtered.length();
        for(;i < l && size > 0;++i,--size)
        {
            filtered[i] = letter;
        }
    }
    //根節點
    TreeNode* rootNode;
    //關鍵詞快取
    std::string keywordBuffer;
};


測試用例

int main()
{
    LocalFileMgr localFileMgr;
    localFileMgr.SetConfigPath("./");
    localFileMgr.Run();//這裡是讀取敏感詞檔案,不是重點,程式碼就不列出來了。敏感詞總個數為7479
    std::vector<std::string>& keywordList = localFileMgr.m_keywordList;
    if(keywordList.empty())
    {
        printf("keywordList empty\n");
        return 0;
    }
    DFA dfa;
    if(!dfa.createKeywordTree(keywordList))
    {
        printf("createKeywordTree failed\n");
        return -1;
    }
    const std::string tofilter = "av女來深圳玩,女教師出來接待";
    std::string filtered;
    printf("tofilter (%s)\n",tofilter.c_str());
    clock_t t = clock();
    int n = 1000000;
    for(int i = 0;i < 1000000;++i)
    {
        dfa.searchKeyword(tofilter,filtered);
    }
    clock_t last = clock();
    double useTime = ((last - t)* 1000) /CLOCKS_PER_SEC ;
    printf("測試敏感詞 tofilter(%s) filtered(%s),use time(%lf)ms,try num(%d)",
                        tofilter.c_str(),filtered.c_str(),useTime,n);
    return 0;
}

測試結果 :測試過濾次數為1000000

 tofilter(av女來深圳玩,女教師出來接待) filtered(*****來深圳玩,*********出來接待),use time(2620.000000)ms,try num(1000000)

每過濾一個關鍵字大概是1.3微秒左右。

本機中分配一個std::string 變數的時間為0.2微秒。

平均過濾含1個關鍵字一個句子的並替換為*號的時間,是分配一個std::string變數花費時間的6倍到7倍左右。


相關推薦

DFA確定性有限狀態過濾敏感

介紹 通常把確定的有窮狀態自動機(有窮狀態自動機也就是本文討論的這種狀態機)稱為DFA,把非確定的有窮狀態自動機稱為NFA。 原理 狀態機就是通過當前狀態state和事件event得到下一個狀態state,即state+event=nextstate DFA確定性有限狀態機

【python 走進NLP】兩種高效過濾敏感演算法--DFA演算法和AC自動機演算法

一道bat面試題:快速替換10億條標題中的5萬個敏感詞,有哪些解決思路? 有十億個標題,存在一個檔案中,一行一個標題。有5萬個敏感詞,存在另一個檔案。寫一個程式過濾掉所有標題中的所有敏感詞,儲存到另一個檔案中。 1、DFA過濾敏感詞演算法 在實現文字過濾的演算法中,DFA是

自動機,狀態有限自動機,有限狀態有限狀態自動機,非確定下有限狀態自動,確定性有限狀態自動機的區別於聯絡

這幾個概念暈了幾天了,搞明白了就來備註一下 FSM(Finite State Machine) FAM(Finite Automata Machine) DFA(Determinate Finite Automata) NFA(Non-Determinate Finite

詞法分析:從RE(正則表示式)到DFA(確定的有限狀態

模式識別(Pattern recognization)是現在非常流行的一個詞,我們對詞法的分析也是基於模式(pattern-based)的。我們用正則表示式(Regular Expression)來定義單詞的模式,而在詞法分析時,有限狀態機(Finite Aut

獨立項目-角色控制器-有限狀態(FSM)

技術分享 OS pos .com com 過渡 動畫過渡 unity 狀態機   學習內容:     Unity5的新動畫系統       1.動畫導入 分動畫       2.創建動畫狀態機       3.設置動畫過渡 設置動畫跳轉條件       4.動畫重定向 獨立

go - FSM(有限狀態)初體驗

層次 time lba 當前 時機 警告 pen nil 自定義 FSM有限狀態機 /** * @Author: wsp * @Time : 2018/4/11 16:45 * @Description: */ package fsmStrudy import

Verilog筆記.3.有限狀態

情況 || mage 參數 lose default def ril 定義 有限狀態機有限狀態機是由寄存器組和組合邏輯構成的硬件時序電路,其狀態(即由寄存器組的1和0的組合狀態所構成的有限個狀態)只可能在同一時鐘跳變沿的情況下才能從一個狀態轉向另一個狀態,究竟轉向哪一狀態還

TCP三次握手、四次端口和有限狀態

TCP三次握手、四次端口和有限狀態機1、TCP用三次握手(three-way handshake) 一對終端同時初始化一個它們之間的連接是可能的。但通常是由一端打開一個套接字(socket)然後監聽來自另一方的連接,這就是通常所指的被動打開(passive open)。服務器端被被動打開以後,用戶端就能開始創

FPGA學習筆記(七)——FSM(Finite State Machine,有限狀態)設計

fault mil 系統 time 編碼 代碼 ril esc 寫法   FPGA設計中,最重要的設計思想就是狀態機的設計思想!狀態機的本質就是對具有邏輯順序和時序規律的事件的一種描述方法,它有三個要素:狀態、輸入、輸出:狀態也叫做狀態變量(比如可以用電機的不同轉速作為狀態

文字版 描述TCP三次握手和四次揮手以及有限狀態

切換 list 遠方 是什麽 int last 關閉 ive tcp報文 客戶端和服務器 ,雙方都處於第一次交互的情況下展開通信 三次握手 1.首先 服務器 需要是處於listen收聽狀態下才能接受報文客戶端由closed狀態 打開並向服務器發送報文SYN=1 申請建

從React Redux的實際業務場景來看有限狀態

寫在前面 上一篇:從Promise的實現來看有限狀態機 上一篇講到了一個簡單的,利用了有限狀態機的前端實現Promise。Promise的有限狀態機除了start以及finish兩個狀態,其核心的三個狀態其實就是一個非同步行為的三種狀態:PENDING、FULFILLED、REJECTED。通過非同步行為

前端狀態管理與有限狀態

原文連結 當下前端流行的框架,都是用狀態來描述介面(state => view),可以說前端開發實際上就是在維護各種狀態(state),這已經成為目前前端開發的共識。 View = ViewModel(Model); 複製程式碼 理想情況下,ViewModel 是純函式,給定相同的 Mod

Verilog_有限狀態

名詞解釋 狀態機就是一種能夠描述具有邏輯順序和時序順序事件的方法。 狀態機有兩大類:Mealy型和Moore型。 Moore型狀態機的輸出只與當前狀態有關,而Mealy型狀態機的輸出不僅取決於當前狀態,還受到輸入的直接控制,並且可能與狀態無關。 當使用Veril

php 去除常見中文停用(過濾敏感)

在用sphinx通過文章標題匹配相關文章時,去除停用詞後調出的文章相關性更好。 <?php header("Content-type:text/html;charset=utf-8"); $str = file_get_contents('stop.txt');//將常見中文停用詞表內容讀入

有限狀態(FSM)的設計

有限狀態機(FSM)的設計_zhangxianhe  有限狀態機(FSM)是一種常見的電路,由時序電路和組合電路組成。  設計有限狀態機的第一步是確定採用Moore 狀態機還是採用Mealy 狀態機。 Mealy 型:狀態的轉變不僅和當前狀態有關,而且跟各輸入訊號有關; Moo

Unity簡單有限狀態實現

【前言】 本篇來說一下關於簡單有限狀態機的演算法實現,當然我們的幾個狀態會比較簡單,而且本身我也是處於入門狀態,所以當成一個簡單的知識積累。有限狀態機,顧名思義,狀態是有限個的,而且狀態之間是關聯的,本篇寫的狀態機,其實是類似寫遊戲裡面的AI機器人,就是那些遊戲

Unity3d 有限狀態

using System.Collections; using System.Collections.Generic; using UnityEngine; //裝換條件 public enum Transition { NullTransition=0, SeePlayer, LostPlaye

有限狀態(FSM)寫法的個人總結(一段式,二段式,三段式)

      狀態機描述時關鍵是要描述清楚幾個狀態機的要素,即如何進行狀態轉移,每個狀態的輸出是什麼,狀態轉移的條件等。具體描述時方法各種各樣,最常見的有三種描述方式:      (1)一段式:整個狀態機寫到一個always模組裡面,在該模組中既描述狀態轉移,又描述狀態的輸入

基於委託與事件的有限狀態設計與實現(Unity3d)

有限狀態機設計與實現 前言 最近看到一句話,就是優秀是一種習慣,所以突然就總想寫點什麼,當作是遊戲開發這條路上的學習筆記吧,另外也時刻提醒自己需要不斷努力。 什麼是狀態機? 先貼百度百科的概念內容吧: 有限狀態機,(英語:Finite-state machine

學習筆記(三) 簡單的狀態模式&FSM有限狀態框架的實現(二)

之前釋出的那篇部落格可能說的並是不非常清楚,所以整理了一下,也參考了一些文件,於是又寫了一篇總結。 一、有限狀態機FSM的要點 1、擁有一組狀態,並且可以再這組狀態之間進行切換。 2、狀態機同時只能存在一個狀態,英雄不能能同時處於跳躍和站立。而防止這一點就是使用