關鍵字過慮實現的思路及Aho–Corasick高效字符串匹配算法應用

既然是要過慮,那就要先查找,如果是直接的一個字符一個字符的匹配,那是很耗時的,因爲時間花在不需要匹配的工作,有不少人會用正則去解決過慮,我09年的時候也這樣,但後來發現大量關鍵詞下性能確實極低下,所以纔會另想它法。上一文中的過慮主要思想是這樣的,開始會先用一個字典保存保存所有關鍵詞,同一個字母開頭的會另放在一個子字典裏,這樣一來,掃描的範圍就大大的縮小了,然後再考慮到髒字一般是2個字的佔了很大的比例,所以再在第二個字母做判斷,如果不存在就不需要再掃描下去了,至於可跳字符,就是在直接需要掃描的時候一一判斷的,沒技巧可講,另外一點值得注意的是,大小寫敏感的情況下,在判斷時需要轉換大小寫,大量關鍵詞影響不小,所以就初始化時再保存了一分小寫的,所以在掃描的時候就不需要轉換了,所以是否大小寫的兩個情況性能上不會有什麼變化,基本的思路就這樣了。如果說,你想要很準確的過慮,那就要用到分詞了(判斷可以人性化),我的方法只能處理比較簡單匹配與過慮。實現過程並沒有使用Aho–Corasick算法。

 

在查找資料的時候還瞭解到Aho–Corasick算法,它可以幫助我們快速的找出多個子字符串。可以到這裏瞭解算法:http://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_string_matching_algorithm

 

5點有約會,先直接上實現代碼了(從一老外的例子裏小改的),本人測試過我上面的方法和使用Aho–Corasick過慮用的時候差不了多少:


代碼如下:

/// <summary>
/// 表示一個查找結果
/// </summary>
public struct KeywordSearchResult
{
    private int index;
    private string keyword;
    public static readonly KeywordSearchResult Empty = new KeywordSearchResult(-1, string.Empty);
 
    public KeywordSearchResult(int index, string keyword)
    {
        this.index = index; 
        this.keyword = keyword;
    }
 
    /// <summary>
    /// 位置
    /// </summary>
    public int Index
    {
        get { return index; }
    }
 
    /// <summary>
    /// 關鍵詞
    /// </summary>
    public string Keyword
    {
        get { return keyword; }
    }
}
 
 
/// <summary>
/// Aho-Corasick算法實現
/// </summary>
public class KeywordSearch
{
    /// <summary>
    /// 構造節點
    /// </summary>
    private class Node
    {
        private Dictionary<char, Node> transDict;
 
        public Node(char c, Node parent)
        {
            this.Char = c;
            this.Parent = parent;
            this.Transitions = new List<Node>();
            this.Results = new List<string>();
 
            this.transDict = new Dictionary<char, Node>();
        }
 
        public char Char
        {
            get;
            private set;
        }
 
        public Node Parent
        {
            get;
            private set;
        }
 
        public Node Failure
        {
            get;
            set;
        }
 
        public List<Node> Transitions
        {
            get;
            private set;
        }
 
        public List<string> Results
        {
            get;
            private set;
        }
 
        public void AddResult(string result)
        {
            if (!Results.Contains(result))
            {
                Results.Add(result);
            }
        }
 
        public void AddTransition(Node node)
        {
            this.transDict.Add(node.Char, node);
            this.Transitions = this.transDict.Values.ToList();
        }
 
        public Node GetTransition(char c)
        {
            Node node;
            if (this.transDict.TryGetValue(c, out node))
            {
                return node;
            }
 
            return null;
        }
 
        public bool ContainsTransition(char c)
        {
            return GetTransition(c) != null;
        }
    }
 
    private Node root; // 根節點
    private string[] keywords; // 所有關鍵詞
 
    public KeywordSearch(IEnumerable<string> keywords)
    {
        this.keywords = keywords.ToArray();
        this.Initialize();
    }
 
    /// <summary>
    /// 根據關鍵詞來初始化所有節點
    /// </summary>
    private void Initialize()
    {
        this.root = new Node(' ', null);
 
        // 添加模式
        foreach (string k in this.keywords)
        {
            Node n = this.root;
            foreach (char c in k)
            {
                Node temp = null;
                foreach (Node tnode in n.Transitions)
                {
                    if (tnode.Char == c)
                    {
                        temp = tnode; break; 
                    }
                }
 
                if (temp == null)
                {
                    temp = new Node(c, n);
                    n.AddTransition(temp);
                }
                n = temp;
            }
            n.AddResult(k);
        }
 
        // 第一層失敗指向根節點
        List<Node> nodes = new List<Node>();
        foreach (Node node in this.root.Transitions)
        {
            // 失敗指向root
            node.Failure = this.root;
            foreach (Node trans in node.Transitions)
            {
                nodes.Add(trans);
            }
        }
        // 其它節點 BFS
        while (nodes.Count != 0)
        {
            List<Node> newNodes = new List<Node>();
            foreach (Node nd in nodes)
            {
                Node r = nd.Parent.Failure;
                char c = nd.Char;
 
                while (r != null && !r.ContainsTransition(c))
                {
                    r = r.Failure;
                }
 
                if (r == null)
                {
                    // 失敗指向root
                    nd.Failure = this.root;
                }
                else
                {
                    nd.Failure = r.GetTransition(c);
                    foreach (string result in nd.Failure.Results)
                    {
                        nd.AddResult(result);
                    }
                }
 
                foreach (Node child in nd.Transitions)
                {
                    newNodes.Add(child);
                }
            }
            nodes = newNodes;
        }
        // 根節點的失敗指向自己
        this.root.Failure = this.root;
    }
 
    /// <summary>
    /// 找出所有出現過的關鍵詞
    /// </summary>
    /// <param name="text"></param>
    /// <returns></returns>
    public List<KeywordSearchResult> FindAllKeywords(string text)
    {
        List<KeywordSearchResult> list = new List<KeywordSearchResult>();
 
        Node current = this.root;
        for (int index = 0; index < text.Length; ++index)
        {
            Node trans;
            do
            {
                trans = current.GetTransition(text[index]);
 
                if (current == this.root)
                    break;
 
                if (trans == null)
                {
                    current = current.Failure;
                }
            } while (trans == null);
 
            if (trans != null)
            {
                current = trans;
            }
 
            foreach (string s in current.Results)
            {
                list.Add(new KeywordSearchResult(index - s.Length + 1, s));
            }
        }
 
        return list;
    }
 
    /// <summary>
    /// 簡單地過慮關鍵詞
    /// </summary>
    /// <param name="text"></param>
    /// <returns></returns>
    public string FilterKeywords(string text)
    {
        StringBuilder sb = new StringBuilder();
 
        Node current = this.root;
        for (int index = 0; index < text.Length; index++)
        {
            Node trans;
            do
            {
                trans = current.GetTransition(text[index]);
 
                if (current == this.root)
                    break;
 
                if (trans == null)
                {
                    current = current.Failure;
                }
 
            } while (trans == null);
 
            if (trans != null)
            {
                current = trans;
            }
 
            // 處理字符
            if (current.Results.Count > 0)
            {
                string first = current.Results[0];
                sb.Remove(sb.Length - first.Length + 1, first.Length -1);// 把匹配到的替換爲**
                sb.Append(new string('*', current.Results[0].Length));
 
            }
            else
            {
                sb.Append(text[index]);
            }
        }
 
        return sb.ToString();
    }
}

原文:http://www.cnblogs.com/kudy/archive/2011/12/20/2294762.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章