再谈脏字过滤(基于hash的优化算法)

前段时间写了篇文章。说的trie算法原文见

http://www.cnblogs.com/yeerh/archive/2011/08/24/2152607.html

回复中有说到一个很快的方法 。原文见xingd的博客

http://www.cnblogs.com/xingd/archive/2008/02/01/1061800.html

 

经测试,这种方法确实很快,但已不是单纯的hash算法了,其中的思路比较巧妙。

文章中基本上只有代码,只有很少的说明。

经过本人的认真阅读,终于搞懂了优化的关键所在。

现在对这种做了一个小的改动,不敢独享,特分享给大家。


这种优化基于我们本常所用的大部分词或是字并不是脏字的前提。

有些字符即使在脏字单词中出现,他们出现地位置也不一样。

 

根据这个特点。我们先建立一个用于快速过滤的数组。

Uint16[] m_fastCheck = new Uint16[char.MaxValue];

 

fastCheck的索引号用来表示对应的字符。

里面的值 表示这个字符在单词中出现的位置,我们用Uint16来表示,
也就可以保存1-16。。

 

比如:马这个字符脏字有 "你马", '马的' , '草泥马'

则 fastCheck[39532] = 0x07;  //''转化为数字为39532

因为字符可以隐式转换为整数。也可以写作 fastCheck[''] = 0x07;

至于这个0x07是怎么来的。因为马出现的位置有 12

对这些位置做位运算就可以得出7.

 

这个我们在添加关键中的时候就可以初始化。

public void AddKey(string word)

{.................

for(int i=0;i<word.Length;i++)

{

    m_fastCheck[word[i]] |= (UInt16)(1<<i);

}

}

 

有了这个,我们可以快速排除一些不是脏字字符。还可以更快吗?

可以,我们再加一个首尾字符的判断。

Uint16[] m_startCheck = new Uint16[char.MaxValue];

Uint16[] m_endCheck = new Uint16[char.MaxValue];

而这种我们保存的不是字符出现的位置。而是保存词的长度。

 

public void AddKey(string word)

{.................

for(int i=0;i<word.Length;i++)

{

    m_fastCheck[word[i]] |= (UInt16)(1<<i);

}

Uint16 mask = (UInt16)(1<<word.Length-1);

m_startCheck[word[0]]  |= mask;

m_endCheck[word[word.Length-1]] |=  mask;

}

 

就这样,在进行查找的时候。我们先判断对应位置的字符出现地位置是否正确,

再判断首尾字符的长度是否要求。如果这些条件都满足,再进行hash查找。

方法如下:

 public string FindOne(string text)

        {

            for (int index = 0; index < text.Length; index++)

            {

                int count = 0;

                int maxIndex = Math.Min(maxWordLength + index, text.Length);

                char begin = text[index];

                for (int j = index; j < maxIndex; j++)

                {

                    char current = text[j];

                    UInt16 mask = (UInt16)(1 << count);

                    //先判断字符出现的位置是否正确

                    if ((m_fastCheck[current] & mask) == 0)

                    {

                        index += count;

                        break;

                    }

                    ++count;

                    //再判断首字符和尾字符的长度是否正确.

                    if ((m_startLength[begin] & mask) > 0 && (m_endLength[current] & mask) > 0)

                    {

                        //进行hash比较

                        .........................

                    }

                }

            }

            return string.Empty;

        }

 

至此,整个查找方法就完成了。脏字替换跟查找的方法差不多。

为了减少字符串的截取,我重写了一个HashSet, 支持bool  Contains(string text,int offset,int coung)的方式进行调用.

 

完整的代码下载:

http://files.cnblogs.com/yeerh/Filter2.7z

 

15:15 2011-12-21 更新版,修正了一些小bug
http://files.cnblogs.com/yeerh/Filter3.7z

 

11:17 2012/10/7 更新版,修正了FastFilter中的一个错误
http://files.cnblogs.com/yeerh/Filter4.7z

 

posted @ 2011-10-20 15:46 边城浪 阅读(...) 评论(...) 编辑 收藏