Увеличение эффективности Regex

Я имею о 100k почтовых объектах Outlook, которые имеют приблизительно 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, чтобы перейти через некоторые узлы и вытащить тело (nSearch. InnerHtml). _keywordList является элементом списка и не массивом.

6
задан cam 30 March 2010 в 13:23
поделиться

10 ответов

Я предполагаю, что 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;
}

Другой оптимизацией может быть оптимизация, предложенная Джеффом Йейтсом. Например, используя один шаблон:

string pattern = @"(\b(?:" + string.Join("|", _keywordList) + @")\b)";
7
ответ дан 8 December 2019 в 18:34
поделиться

Чаще всего возникают совпадения, которые терпят неудачу, поэтому вы хотите свести к минимуму сбои.

Если поисковое ключевое слово встречается нечасто, вы можете проверить их все одновременно (с помощью regexp \ b (aaa | bbb | ccc | ....) \ b ), тогда вы исключаете электронные письма без совпадений. Тот, у которого есть хотя бы одно совпадение, вы проделаете тщательный поиск.

2
ответ дан 8 December 2019 в 18:34
поделиться

Этот может быть быстрее. Вы можете использовать группы регулярных выражений следующим образом:

    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]);

ОБНОВЛЕНИЕ:

После повторного просмотра моего кода я просто понял, что помещать совпадения в группы действительно не нужно. И у Regex 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;
}    
1
ответ дан 8 December 2019 в 18:34
поделиться

Если вы можете использовать .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
    }
}
0
ответ дан 8 December 2019 в 18:34
поделиться

Если поиск по ключевым словам ведется по прямым литералам, т. Е. Не содержит дополнительных совпадений с шаблонами регулярных выражений, то другой метод может быть более подходящим. Следующий код демонстрирует один такой метод, этот код проходит через каждое электронное письмо только один раз, ваш код проходит через каждое электронное письмо 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;
        }
0
ответ дан 8 December 2019 в 18:34
поделиться

Регулярные выражения можно немного оптимизировать, если вы просто хотите сопоставить с фиксированным набором постоянных строк. Вместо нескольких совпадений, например против «зимы», «победы» или «вомбата», вы можете просто сопоставить с «w (in (ter)? | ombat)» , например (книга Джеффри Фридла может дать вам множество идей, таких как это). Такая оптимизация также встроена в некоторые программы, особенно в emacs ('regexp-opt'). Я не слишком знаком с .NET, но полагаю , что кто-то запрограммировал аналогичные функции - Google для «оптимизации регулярных выражений».

0
ответ дан 8 December 2019 в 18:34
поделиться

Если регулярное выражение действительно является узким местом, и даже его оптимизация (путем объединения поисковых слов в одно выражение) не помогает, рассмотрите возможность использования алгоритма поиска с несколькими шаблонами, такого как Wu -Манбер.

Я опубликовал очень простую реализацию здесь, в Stack Overflow. Он написан на C ++, но поскольку код прост, его будет легко перевести на C #.

Обратите внимание, что при этом слова будут найдены где угодно , а не только на границах слов. Однако это можно легко проверить после того, как вы проверите, содержит ли текст какие-либо слова; либо снова с помощью регулярного выражения (теперь вы проверяете только отдельные электронные письма - намного быстрее), либо вручную, проверяя символы до и после отдельных обращений.

0
ответ дан 8 December 2019 в 18:34
поделиться

одну вещь, которую вы можете легко сделать, - это сопоставить все слова за один присест, построив такое выражение, как:

\ b (?: Word1 | word2 | word3 | ....) \ b

Тогда вы может предварительно скомпилировать шаблон и повторно использовать его для поиска всех вхождений для каждого электронного письма (не знаю, как вы это делаете с .Net API, но должен быть способ).

Другое дело, вместо использования флага ignorecase, если вы конвертируете все в нижний регистр, это может дать вам небольшой прирост скорости (необходимо профилировать его, поскольку это зависит от реализации). Не забывайте разогревать CLR при профилировании.

1
ответ дан 8 December 2019 в 18:34
поделиться

Я не думаю, что это работа с регулярными выражениями. Возможно, вам будет лучше искать в каждом сообщении слово за словом и сравнивать каждое слово со своим списком слов. При таком подходе вы просматриваете каждое сообщение n раз, где n - количество слов, которые вы хотите найти - неудивительно, что на это требуется время.

2
ответ дан 8 December 2019 в 18:34
поделиться

Если ваша проблема заключается в поиске элементов Outlook, содержащих определенную строку, вы должны получить выгоду от использования средств поиска Outlook ...

см .: http://msdn.microsoft.com / en-us / library / bb644806.aspx

0
ответ дан 8 December 2019 в 18:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: