C # регулярное выражение с пробелами слишком медленно - PullRequest
0 голосов
/ 26 декабря 2011

Я использую WinForms NET 2.0 в C #.

У меня есть текстовые файлы, около 1000-1500 строк.Некоторые строки в них начинаются с 4 или более буквенных слов, и я должен добавить двоеточие к этим словам.Наличие пробела в начале этих строк не является обязательным, и строка может содержать больше текста, кроме этих слов.Вот пример:

    lda $00,x
    mov $20
    rep #$20
    tax
    lda #$0000,y
word
    ...         ; comment
  anotherword           ; this word has whitespace before it.

Кроме того, если двоеточие уже есть, оно просто игнорирует их, чтобы предотвратить добавление новых.Вот мой код:

Regex R = new Regex(@"^\s*(?<word>[A-Za-z0-9_]{4,})", RegexOptions.Multiline); //keep the words stored in a group called word
MatchCollection M = R.Matches(txt); //let my text file string be "txt"

foreach (Match m in M)
{
    string mm = m.Groups["word"].Value;
    if (!Regex.IsMatch(txt, @"^\s*\b" + mm + @"\b:", RegexOptions.Multiline)) // if already a colon, return
        txt = Regex.Replace(txt, @"^\s*\b" + mm + @"\b", mm + ":", RegexOptions.Multiline);
}

Работает и все, но проблема?Это слишком медленно.Я выполняю другие операции в текстовом файле, но я подтвердил, что они быстрые, и проблема заключается в двух "\ s *" в моем регулярном выражении выше.Когда я удаляю их обоих, поиск становится в 10 раз быстрее.

Как я могу это исправить?

Ответы [ 2 ]

3 голосов
/ 26 декабря 2011

Альтернативное решение @ TimPietzcker:

result = Regex.Replace(subject, @"^(?>(\s*\w{4,}))(?!:)", "$1:", RegexOptions.Multiline);

, где (?>...) - атомная группировка. Когда механизм регулярных выражений входит в атомарную группировку, ему не разрешается возвращаться назад нигде во входе, который использовала эта группировка.

Теперь, почему это полезно? Рассмотрим строку:

             ab3 #13 spaces, then a, b, 3

Если вы не используете атомарную группировку, когда регулярное выражение не соответствует 4-му символу во втором квантификаторе, оно должно вернуться к символу до a: но это пробел, он не совпадает. И так до тех пор, пока он не достигнет символа перед началом строки, где ^ не совпадает, только после этого объявляется ошибка (\s* может соответствовать пустой строке).

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

2 голосов
/ 26 декабря 2011

Здесь я вижу три основные проблемы:

  1. Вы выполняете одно и то же совпадение с регулярным выражением до трех раз в каждой строке.Как показал Тим, вам не нужно касаться какой-либо строки более одного раза, независимо от того, соответствует она регулярному выражению или нет.Кроме того, вам никогда не нужно проверять строку с помощью Match () или IsMatch () перед выполнением Replace () с тем же регулярным выражением.Если строка не соответствует регулярному выражению, Replace () просто вернет ее без изменений.

  2. Нет необходимости создавать строку замены, как вы делаете сейчас.Для этого и нужны группы захвата.

  3. \s соответствует всем пробельным символам, включая переводы строки.Если есть (например) девять пустых строк, за которыми следует совпадающая строка, регулярное выражение будет использовать все десять строк.Если десятая строка * не совпадает, механизм регулярных выражений откажется от этой попытки сопоставления и попытается снова начать со второй пустой строки.И снова в третьей строке, в четвертой строке и т. Д. Если удаление \s* из вашего регулярного выражения имело большой эффект, это, вероятно, причина: он пытается излишне сопоставить большое количество пробелов.Если вы знаете, что искомые строки всегда будут в одной строке, вы должны убедиться, что регулярное выражение соответствует только горизонтальным пробелам, то есть пробелам и символам табуляции.

Для демонстрации:

result = Regex.Replace(subject, @"(?m)^([ \t]*\w{4,})(?![\w:])", "$1:");

Для объяснения:

  • (?m) - это просто более удобный способ указать параметр Multiline.
  • ^([ \t]*\w{4,}) соответствует первому слову в строке вместе с любым начальным пробелом и захватывает все это в группе № 1.
  • (?![\w:]) - отрицательный прогноз;он утверждает, что следующий символ (если он есть) не является ни символом слова, ни двоеточием.Это гарантирует, что вы использовали все слово, а за словом не следует двоеточие.
  • В аргументе замены $1 является заполнителем для содержимого первой группы захвата.

Я заметил, что ваше регулярное выражение соответствует лидирующему пробелу, не захватывая его, и вы не добавляете ничего в замену.В результате удаляется начальный пробел из любой строки, на которой вы выполняете эту замену, но не из любых других строк.Если это действительно то, что вы хотите, вы можете изменить ^([ \t]*\w{4,}) на ^[ \t]*(\w{4,}).

...