Подтвердить что ты не робот

Regex принимает удивительно долгое время

У меня есть строка поиска, введенная пользователем. Обычно строка поиска разделяется с использованием пробелов, а затем выполняется поиск OR (элемент соответствует, если он соответствует любому элементу строки поиска). Я хочу предоставить несколько "расширенных" функций запроса, таких как возможность использования кавычек для включения буквенных фраз, содержащих пробельные символы.

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

Я надеялся, что у кого-то может быть некоторое представление о том, как я могу немного ускорить это регулярное выражение. Я знаю, что я использую lookbehind с неограниченным квантификатором, но, как я уже сказал, это не вызывает каких-либо проблем с производительностью до тех пор, пока не будет сопоставлено последнее совпадение.

КОД

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace RegexSandboxCSharp {
    class Program {
        static void Main( string[] args ) {

            string l_input1 = "# one  \"two three\" four five:\"six seven\"  eight \"nine ten\"";

            string l_pattern =
                @"(?<=^([^""]*([""][^""]*[""])?)*)\s+";

            Regex l_regex = new Regex( l_pattern );

            MatchCollection l_matches = l_regex.Matches( l_input1 );
            System.Collections.IEnumerator l_matchEnumerator = l_matches.GetEnumerator();

            DateTime l_listStart = DateTime.Now;
            List<string> l_elements = new List<string>();
            int l_previousIndex = 0;
            int l_previousLength = 0;
            //      The final MoveNext(), which returns false, takes 2 seconds.
            while ( l_matchEnumerator.MoveNext() ) {
                Match l_match = (Match) l_matchEnumerator.Current;
                int l_start = l_previousIndex + l_previousLength;
                int l_length = l_match.Index - l_start;
                l_elements.Add( l_input1.Substring( l_start, l_length ) );

                l_previousIndex = l_match.Index;
                l_previousLength = l_match.Length;
            }
            Console.WriteLine( "List Composition Time: " + ( DateTime.Now - l_listStart ).TotalMilliseconds.ToString() );

            string[] l_terms = l_elements.ToArray();

            Console.WriteLine( String.Join( "\n", l_terms ) );

            Console.ReadKey( true );

        }
    }
}

OUTPUT
(Это именно то, что я получаю.)

     

один
   "два три"
  четыре
  пять: "шесть семь"
  восемь
   "девять десять"

4b9b3361

Ответ 1

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

(?<=^((?>[^"]*)(["][^"]*["])?)*)\s+

Единственное изменение здесь - поставить [^"]* в атомную группу , которая предотвращает катастрофическое обратное отслеживание.

Примечание: Регулярное выражение, очевидно, не использует синтаксис строки регулярного выражения С#, с которым я не знаком, но я думаю, что это было бы следующим:

@"(?<=^((?>[^""]*)([""][^""]*[""])?)*)\s+";

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

В этот момент регулярное выражение внутри lookbehind начнет возвращаться. Якорь означает, что он всегда будет начинаться в начале строки, но он все равно может отступить, сбросив элементы с конца того, что он сопоставил. Давайте посмотрим на регулярное выражение внутри lookbehind:

^([^"]*(["][^"]*["])?)*

Так как цитируемые разделы являются необязательными, они могут быть отброшены, поскольку обратное перемещение регулярных выражений. Для каждого фрагмента символов без кавычек, которые не находятся внутри кавычки, перед обратным следом каждый символ был бы согласован как часть [^"]* в начале регулярного выражения. Когда в этом разделе начинается откат, последний символ будет сброшен из того, что соответствует [^"]*, и будет подхвачено внешним повторением. В этот момент он становится очень похожим на пример в катастрофической ссылке обратного слежения выше.