提高正则表达效率

时间:2010-03-30 13:23:05

标签: c# regex

我有大约100k个Outlook邮件项目,每个Body有大约500-600个字符。我有一个包含580个关键字的列表,必须搜索每个正文,然后在底部附加单词。

我相信我已经提高了大部分功能的效率,但仍然需要花费很多时间。即使是100封电子邮件也需要4秒钟。

我为每个关键字列表运行两个函数(每个列表有290个关键字)。

       public List<string> Keyword_Search(HtmlNode nSearch)
    {
        var wordFound = new List<string>();
        foreach (string currWord in _keywordList)
        {
            bool isMatch = Regex.IsMatch(nSearch.InnerHtml, "\\b" + @currWord + "\\b",
                                                  RegexOptions.IgnoreCase);
            if (isMatch)
            {
                wordFound.Add(currWord);
            }
        }
        return wordFound;
    }

无论如何我可以提高这个功能的效率吗?

另一件可能会减慢速度的事情是我使用HTML Agility Pack来浏览某些节点并拉出正文(nSearch.InnerHtml)。 _keywordList是List项,而不是数组。

10 个答案:

答案 0 :(得分:7)

我认为COM调用nSearch.InnerHtml非常慢,你重复调用你正在检查的每个单词。您只需缓存调用结果:

public List<string> Keyword_Search(HtmlNode nSearch)
{
    var wordFound = new List<string>();

    // cache inner HTML
    string innerHtml = nSearch.InnerHtml;

    foreach (string currWord in _keywordList)
    {
        bool isMatch = Regex.IsMatch(innerHtml, "\\b" + @currWord + "\\b",
                                              RegexOptions.IgnoreCase);
        if (isMatch)
        {
            wordFound.Add(currWord);
        }
    }
    return wordFound;
}

另一项优化将是Jeff Yates建议的优化。例如。使用单一模式:

string pattern = @"(\b(?:" + string.Join("|", _keywordList) + @")\b)";

答案 1 :(得分:2)

我不认为这是正则表达式的工作。你可能最好逐个字地搜索每个消息,并根据你的单词列表检查每个单词。使用您拥有的方法,您将搜索每条消息n次,其中n是您要查找的单词数量 - 难怪它需要一段时间。

答案 2 :(得分:2)

大多数情况下,表单匹配失败,因此您希望尽量减少失败。

如果搜索关键字不频繁,您可以同时测试所有这些关键字(使用正则表达式\b(aaa|bbb|ccc|....)\b),然后排除没有匹配项的电子邮件。至少有一场比赛,你进行彻底的搜索。

答案 3 :(得分:1)

你可以轻松做的一件事就是通过建立一个表达式来匹配所有单词:

\ B(:字词1 | WORD2 | WORD3 | ....)\ B'/ P>

然后你可以预先编译模式并重复使用它来查找每封电子邮件的所有出现(不确定如何使用.Net API执行此操作,但必须有一种方法)。

另一件事是不使用ignorecase标志,如果你将所有内容转换为小写,这可能会给你一个小的速度提升(需要根据它的实现依赖性来分析它)。在分析时不要忘记预热CLR。

答案 4 :(得分:1)

可能更快。您可以像这样使用Regex Groups:

    public List<string> Keyword_Search(HtmlNode nSearch)
    {
        var wordFound = new List<string>();

        // cache inner HTML
        string innerHtml = nSearch.InnerHtml;
        string pattern = "(\\b" + string.Join("\\b)|(\\b", _keywordList) + "\\b)";
        Regex myRegex = new Regex(pattern, RegexOptions.IgnoreCase);
        MatchCollection myMatches = myRegex.Matches(innerHtml);

        foreach (Match myMatch in myMatches)
        {
            // Group 0 represents the entire match so we skip that one
            for (int i = 1; i < myMatch.Groups.Count; i++)
            {
                if (myMatch.Groups[i].Success)
                    wordFound.Add(_keywordList[i-1]);
            }
        }

        return wordFound;
    }    

这样你只使用一个正则表达式。并且组的索引应该与_keywordList相关联,偏移量为1,因此行wordFound.Add(_keywordList[i-1]);

更新:

再次查看我的代码之后,我才意识到将匹配放入Groups中是非常必要的。而正则表达式组有一些开销。相反,您可以从模式中删除括号,然后只需将匹配项本身添加到wordFound列表中。这会产生相同的效果,但速度会更快。

它是这样的:

public List<string> Keyword_Search(HtmlNode nSearch)
{
    var wordFound = new List<string>();

    // cache inner HTML
    string innerHtml = nSearch.InnerHtml;
    string pattern = "\\b(?:" + string.Join("|", _keywordList) + ")\\b";
    Regex myRegex = new Regex(pattern, RegexOptions.IgnoreCase);
    MatchCollection myMatches = myRegex.Matches(innerHtml);

    foreach (Match myMatch in myMatches)
    {
        wordFound.Add(myMatch.Value);
    }

    return wordFound;
}    

答案 5 :(得分:0)

当您只想匹配一组固定的常量字符串时,可以对正则表达式进行相当优化。而不是几个匹配,例如例如,对抗“冬天”,“胜利”或“袋熊”,你可以与"w(in(ter)?|ombat)"匹配(杰弗里弗里德的书可以给你很多这样的想法)。这种优化也包含在一些程序中,特别是emacs('regexp-opt')。我不太熟悉.NET,但我认为某人编写了类似的功能 - 谷歌进行“正则表达式优化”。

答案 6 :(得分:0)

如果正则表达式确实是瓶颈,甚至优化它(通过将搜索词连接到一个表达式)没有帮助,请考虑使用多模式搜索算法,例如武曼伯。

Stack overflow上的

I’ve posted a very simple implementation。它是用C ++编写的,但由于代码很简单,因此很容易将其转换为C#。

请注意,这会在任何地方找到单词,而不仅仅是在单词边界。但是,在 之后,您可以轻松地对此进行测试,并检查文本是否包含任何字词;要么再次使用正则表达式(现在只测试单个电子邮件 - 要快得多),要么通过检查单个点击之前和之后的字符来手动。

答案 7 :(得分:0)

如果您的问题是搜索包含某些字符串的Outlook项目,您应该从使用outlooks搜索工具中获益...

请参阅: http://msdn.microsoft.com/en-us/library/bb644806.aspx

答案 8 :(得分:0)

如果您的关键字搜索是直接文字,即不包含进一步的正则表达式模式匹配,那么其他方法可能更合适。下面的代码演示了一种这样的方法,这段代码只遍历每封电子邮件一次,你的代码经过每封电子邮件290次(两次)

        public List<string> FindKeywords(string emailbody, List<string> keywordList)
        {
            // may want to clean up the input a bit, such as replacing '.' and ',' with a space
            // and remove double spaces

            string emailBodyAsUppercase = emailbody.ToUpper();

            List<string> emailBodyAsList = new List<string>(emailBodyAsUppercase.Split(' '));

            List<string> foundKeywords = new List<string>(emailBodyAsList.Intersect(keywordList));


            return foundKeywords;
        }

答案 9 :(得分:0)

如果您可以使用.Net 3.5+和LINQ,您可以这样做。

public static class HtmlNodeTools
{
    public static IEnumerable<string> MatchedKeywords(
        this HtmlNode nSearch,
             IEnumerable<string> keywordList)
    {
        //// as regex
        //var innerHtml = nSearch.InnerHtml;
        //return keywordList.Where(kw =>
        //       Regex.IsMatch(innerHtml, 
        //                     @"\b" + kw + @"\b",
        //                     RegexOptions.IgnoreCase)
        //        );

        //would be faster if you don't need the pattern matching
        var innerHtml = ' ' + nSearch.InnerHtml + ' ';
        return keywordList.Where(kw => innerHtml.Contains(kw));
    }
}
class Program
{
    static void Main(string[] args)
    {
        var keyworkList = new string[] { "hello", "world", "nomatch" };
        var h = new HtmlNode()
        {
            InnerHtml = "hi there hello other world"
        };

        var matched = h.MatchedKeywords(keyworkList).ToList();
        //hello, world
    }
}

...重用正则表达式示例......

public static class HtmlNodeTools
{
    public static IEnumerable<string> MatchedKeywords(
        this HtmlNode nSearch,
             IEnumerable<KeyValuePair<string, Regex>> keywordList)
    {
        // as regex
        var innerHtml = nSearch.InnerHtml;
        return from kvp in keywordList
               where kvp.Value.IsMatch(innerHtml)
               select kvp.Key;
    }
}
class Program
{
    static void Main(string[] args)
    {
        var keyworkList = new string[] { "hello", "world", "nomatch" };
        var h = new HtmlNode()
        {
            InnerHtml = "hi there hello other world"
        };

        var keyworkSet = keyworkList.Select(kw => 
            new KeyValuePair<string, Regex>(kw, 
                                            new Regex(
                                                @"\b" + kw + @"\b", 
                                                RegexOptions.IgnoreCase)
                                                )
                                            ).ToArray();

        var matched = h.MatchedKeywords(keyworkSet).ToList();
        //hello, world
    }
}