Жадность против неохотно против притяжательных квантификаторов

257

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

В частности, в следующем примере:

Enter your regex: .*foo  // greedy quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.

Enter your regex: .*?foo  // reluctant quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfoo" starting at index 0 and ending at index 4.
I found the text "xxxxxxfoo" starting at index 4 and ending at index 13.

Enter your regex: .*+foo // possessive quantifier
Enter input string to search: xfooxxxxxxfoo
No match found.

В объяснении упоминается еда всей входной строки, буквы потребляемые, отладка отступ, самое правое появление "foo" было срыгивание и т.д.

К сожалению, несмотря на приятные метафоры, я до сих пор не понимаю, что есть... Знаете ли вы о другом учебнике, который объясняет (кратко), как работают механизмы регулярных выражений?

В качестве альтернативы, если кто-то может объяснить в несколько иной формулировке следующий абзац, это было бы высоко оценено:

В первом примере используется жадный квантификатор. * найти "что угодно", ноль или более, а затем буквы "f" "o" "o". Поскольку квантификатор жадные, *. выражение сначала съедает весь вход строка. На этом этапе общий выражение не может быть успешным, поскольку последние три буквы ( "f" "o" "o" ) имеют уже потребляется (кем?). Итак, помощник медленно отступает ( справа налево?) по одной букве за раз до тех пор, пока "foo" было опрокинуто ( что это означает?), при котором чтобы совпадение совпадало, и поиск заканчивается.

Второй пример, однако, неохотно, поэтому он начинается с первого ( кем?) "ничего". Потому что "foo" не появляется в начале строка, она вынуждена усвоить ( кто ласточки?) первая буква ( "x" ), которая запускает первый матч в 0 и 4. Наш тест Жгут проводов продолжается до тех пор, пока входная строка исчерпана. Это находит другое совпадение в 4 и 13.

В третьем примере не удается найти потому что квантификатор притяжательный падеж. В этом случае весь строка ввода потребляется. * +, ( как?) не оставляя ничего, чтобы удовлетворить "foo" в конце выражение. Использовать притяжательную квантификатор для ситуаций, когда вы хочу захватить все что угодно без когда-либо отступать (, что означает отключение?); он превзойдет эквивалентный жадный квантор в случаи, когда матч не немедленно найден.

  • 20
    Максимальные квантификаторы, такие как * , + и ? жадные Минимальные квантификаторы, такие как *? +? и ?? ленивы Асессивные квантификаторы типа *+ , ++ и ?+ Являются липкими.
  • 6
    Этот вопрос был добавлен в FAQ по регулярному выражению переполнения стека в разделе «Квантификаторы> Подробнее о различиях ...».
Теги:
regex-greedy

7 ответов

369
Лучший ответ

Я сделаю это.

A жадный квантификатор сначала соответствует как можно больше. Таким образом, .* соответствует всей строке. Затем помощник пытается сопоставить f, но никаких символов не осталось. Таким образом, он "отступает", заставляя жадный квантификатор соответствовать еще одному (оставляя "o" в конце строки непревзойденным). Это все еще не соответствует f в регулярном выражении, поэтому он "отступает" еще на один шаг, заставляя жадный квантификатор снова совпадать с чем-то другим (оставляя "oo" в конце строки непревзойденным). Это все еще не соответствует f в регулярном выражении, поэтому он возвращает еще один шаг (оставляя "foo" в конце строки непревзойденным). Теперь совпадение окончательно совпадает с f в регулярном выражении, а также теги o и next o. Успех!

A неохотный или "нежеланный" квантификатор сначала соответствует как можно меньше. Таким образом, .* сначала ничего не соответствует, оставляя всю строку непревзойденной. Затем помощник пытается сопоставить f следующее, но непревзойденная часть строки начинается с "x", так что это не сработает. Таким образом, ответчик отступает, делая нежеланный квантор совпадающим с чем-то еще (теперь он соответствует "x", оставляя "fooxxxxxxfoo" непревзойденным). Затем он пытается сопоставить f, который преуспевает, а также o и следующий o в совпадении регулярных выражений. Успех!

В вашем примере он затем запускает процесс с оставшейся непревзойденной частью строки, следуя тому же процессу.

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

  • 14
    +1 Хороший ответ. Я хотел бы только добавить: иди читать освоение регулярных выражений (3-е издание)
  • 0
    @ Anomie, немного опоздал, но в притяжательной части, я думаю, вы имели в виду, так что все начинается с .*+ (Обратите внимание на «+»)
Показать ещё 6 комментариев
23

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

Важное значение для большинства двигателей регулярных выражений состоит в том, что они возвращаются: они будут условно принимать потенциальное, частичное совпадение, пытаясь сопоставить все содержимое регулярного выражения. Если регулярное выражение не может быть полностью сопоставлено с первой попытки, то механизм регулярных выражений будет возвращаться в одно из совпадений. Он будет пытаться повторить повторение *, +, ?, чередование или {n,m} по-разному и повторить попытку. (И да, этот процесс может занять много времени.)

В первом примере используется жадный квантификатор. * найти "что угодно", ноль или более, а затем буквы "f" "o" "o". Поскольку квантификатор жадные, *. выражение сначала съедает весь вход строка. На этом этапе общий выражение не может быть успешным, поскольку последние три буквы ( "f" "o" "o" ) имеют уже потребляется (кем?).

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

Итак, совпадение медленно отступает (справа налево?) по одной букве за раз до тех пор, пока "foo" было опрокинуто ( что это значит?), при котором

Это означает, что foo предусмотрительно включал при сопоставлении .*. Поскольку эта попытка не удалась, механизм регулярных выражений пытается принять меньшее количество символов в .*. Если в этом примере было успешное совпадение перед .*, то, вероятно, двигатель попытался бы сократить совпадение .* (справа налево, как вы указали, потому что это жадный классификатор), и если он не смог сопоставить все входы, тогда он может быть вынужден переоценить то, что он сопоставил до .* в моем гипотетическом примере.

укажите совпадение, и поиск заканчивается.

Второй пример, однако, неохотно, поэтому он начинается с первого (кем?) "ничего". Поскольку "foo"

Исходное ничто не потребляется .?*, которое будет потреблять кратчайшее возможное количество всего, что позволяет соответствовать остальному регулярному выражению.

не появляется в начале строка, она вынуждена усвоить ( кто ласточки?)

Опять же, .?* потребляет первый символ, после того, как он отменил первоначальный сбой, чтобы соответствовать всему регулярному выражению с наименьшим возможным совпадением. (В этом случае двигатель регулярного выражения увеличивает соответствие для .*? слева направо, потому что .*? неохотно.)

первая буква ( "x" ), которая запускает первый матч в 0 и 4. Наш тест Жгут проводов продолжается до тех пор, пока входная строка исчерпана. Это находит другое совпадение в 4 и 13.

В третьем примере не удается найти потому что квантификатор притяжательный падеж. В этом случае весь строка ввода потребляется. * +, (как?)

A .*+ будет потреблять как можно больше и не будет возвращаться, чтобы найти новые совпадения, когда регулярное выражение в целом не сможет найти совпадение. Поскольку притяжательная форма не выполняет backtracking, вы, вероятно, не увидите много применений с .*+, а скорее с классами символов или схожими ограничениями: account: [[:digit:]]*+ phone: [[:digit:]]*+.

Это может значительно ускорить согласование регулярных выражений, потому что вы сообщаете движку регулярных выражений, что он никогда не должен возвращаться к потенциальным совпадениям, если вход не совпадает. (Если вам нужно было написать весь соответствующий код вручную, это будет похоже на то, что никогда не будет использовать putc(3), чтобы "отбросить" входной символ. Он был бы очень похож на наивный код, который можно было бы написать с первой попытки. двигатели регулярных выражений лучше, чем один символ обратной связи, они могут перематывать все обратно на ноль и повторять попытку.:)

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

не оставляя ничего, чтобы удовлетворить "foo" в конце выражение. Использовать притяжательную квантификатор для ситуаций, когда вы хочу захватить все что угодно без когда-либо отступать (, что означает отключение?); он превзойдет

"Откат" в этом контексте означает "откат" - отбрасывание пробного частичного совпадения, чтобы попробовать другое частичное совпадение, которое может или не может быть выполнено.

эквивалентный жадный квантор в случаи, когда матч не немедленно найден.

  • 2
    Я подозреваю, что никогда не бывает случая, когда собственнический квантификатор будет соответствовать чему-то, чего не подойдет жадный квантификатор. Я полагаю, что следующее доказывает это: жадный квантификатор всегда совпадает настолько, насколько это возможно, затем возвращается, если не может найти совпадение. Собственный квантификатор соответствует как можно большему количеству, а затем выходит, если не может найти совпадения. Таким образом, может существовать что-то, что соответствует жадному квантификатору, а не собственно квантификатору, но не наоборот, потому что они оба ищут «дерево» в одной и той же последовательности, квантификатор притяжений просто сдается легче. ;)
  • 2
    Подтверждено: «Для этого нужны атомная группировка и собственнические квантификаторы: эффективность за счет запрета возврата». from регулярные-выражения.info Так что утверждение в этом ответе «Но больше, чем потенциальные ускорения, это также может позволить вам писать регулярные выражения, которые точно соответствуют тому, что вам нужно для соответствия». на самом деле не совсем точно.
Показать ещё 1 комментарий
19

Это просто моя практика для визуализации сцены -

Изображение 4749

  • 1
    Очень полезно для визуальных учеников :)
  • 3
    За исключением того, что я думаю, что последний случай, притяжательный, не должен иметь n проходов - просто захватите всю строку сразу.
Показать ещё 1 комментарий
18

http://swtch.com/~rsc/regexp/regexp1.html

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

Если вы хотите получить более высокий уровень (менее подробное объяснение), для простых регулярных выражений, таких как тот, который вы просматриваете, механизм регулярных выражений работает путем обратного отслеживания. По сути, он выбирает ( "ест" ) раздел строки и пытается сопоставить регулярное выражение с этим разделом. Если это соответствует, отлично. Если нет, двигатель изменяет свой выбор в разделе строки и пытается сопоставить регулярное выражение с этим разделом и так далее, пока он не попытается сделать все возможное.

Этот процесс используется рекурсивно: в попытке сопоставить строку с заданным регулярным выражением, механизм разделит регулярное выражение на части и применит алгоритм к каждой части отдельно.

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

  • Жадный квантификатор сообщает движку начать со всей строки (или, по крайней мере, все, что еще не было согласовано с предыдущими частями регулярного выражения) и проверить, соответствует ли она регулярному выражению. Если это так, отлично; двигатель может продолжить работу с остальным регулярным выражением. Если нет, он снова пытается, но обрезает один символ (последний) с участка проверяемой строки. Если это не сработает, он отключит другой символ и т.д. Таким образом, жадный квантификатор проверяет возможные совпадения в порядке от самого длинного до кратчайшего.

  • Квантователь с неохотой говорит двигателю начать с кратчайшего возможного фрагмента строки. Если он соответствует, двигатель может продолжить работу; если нет, он добавляет один символ в раздел проверяемой строки и пытается это и так далее, пока не найдет совпадение, или вся строка не была использована. Таким образом, неохотный квантификатор проверяет возможные совпадения от кратчайшего до самого длинного.

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

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

  • 1
    Это отличный источник. Я люблю диаграммы состояний машин. :)
  • 0
    @Regex Rookie: рад, что вам это нравится :) Однако, просмотрев этот сайт, я думаю, что должен пояснить, что его целью является продвижение альтернативной реализации механизма регулярных выражений. Алгоритм возврата, который я (частично) и другие ответы описываю, является медленным ; это совершенно отдельный алгоритм от идеи NFA / DFA, описанной на веб-странице. Отступить назад проще для понимания, именно поэтому регулярные выражения обычно объясняются новичкам.
Показать ещё 3 комментария
9

Вот мой прием с использованием позиций Cell и Index (см. диаграмму здесь, чтобы отличить ячейку от индекса).

Жадный - Сопоставьте как можно больше с жадным квантором и всем регулярным выражением. Если совпадения нет, откат от жадного квантификатора.

Строка ввода: xfooxxxxxxfoo
Regex:. * Foo

Вышеупомянутое Regex состоит из двух частей:
(i) '. *' и
(ii) 'foo'

Каждый из этих шагов будет проанализировать две части. Дополнительные комментарии для матча "Pass" или "Fail" объясняются в фигурных скобках.

Шаг 1:
(i). * = xfooxxxxxxfoo - PASS ('. *' - жадный квантификатор и будет использовать всю строку ввода)
(ii) foo = Отсутствует символ, оставшийся после индекса 13 - FAIL
Ошибка совпадения.

Шаг 2:
(i). * = xfooxxxxxxfo - PASS (Возврат к жадному квантификатору. * ')
(ii) foo = o - FAIL
Ошибка совпадения.

Шаг 3:
(i). * = xfooxxxxxxf - PASS (Откат по жадному квантификатору '. *')
(ii) foo = oo - FAIL
Ошибка совпадения.

Шаг 4:
(i). * = xfooxxxxxx - PASS (Откат по жадному квантору '. *')
(ii) foo = foo - PASS
Отчет MATCH

Результат: 1 матч
Я нашел текст "xfooxxxxxxfoo", начинающийся с индекса 0 и заканчивающийся индексом 13.

Неохотно - сравните как можно меньше с неохотным квантором и сравните все регулярное выражение. если нет совпадения, добавьте символы в неохотный квантификатор.

Строка ввода: xfooxxxxxxfoo
Regex:. *? Foo

Вышеупомянутое регулярное выражение состоит из двух частей:
(i) '. *?' и
(ii) 'foo'

Шаг 1:
. *? = '' (пробел) - PASS (сравните как можно меньше с квантором неопределенности). *? '. Индекс 0 с' 'является совпадением.)
foo = xfo - FAIL (ячейка 0,1,2 - индекс от 0 до 3)
Ошибка совпадения.

Шаг 2:
. *? = x - PASS (добавление символов в квантификатор неохоты). *? '. Ячейка 0, имеющая "x", является совпадением.)
foo = foo - PASS
Отчет MATCH

Шаг 3:
. *? = '' (пусто) - PASS (сравните как можно меньше с квантором неопределенности). *? '. Указатель 4 с' 'является совпадением.
foo = xxx - FAIL (ячейка 4,5,6 - индекс от 4 до 7)
Ошибка совпадения.

Шаг 4:
. *? = x - PASS (добавление символов в квантификатор неохоты). *? '. Ячейка 4.)
foo = xxx - FAIL (ячейка 5,6,7 - то есть между 5 и 8)
Ошибка совпадения.

Шаг 5:
. *? = xx - PASS (добавление символов в квантификатор неохотно). *? '. Ячейка 4 по 5.)
foo = xxx - FAIL (ячейка 6,7,8 - то есть между 6 и 9)
Ошибка совпадения.

Шаг 6:
. *? = xxx - PASS (добавление символов в квантификатор неохотно). *? '. Ячейка 4 по 6.)
foo = xxx - FAIL (ячейка 7,8,9 - индекс между 7 и 10)
Ошибка совпадения.

Шаг 7:
. *? = xxxx - PASS (добавление символов в квантификатор неохоты). *? '. Ячейка 4 по 7.)
foo = xxf - FAIL (ячейка 8,9,10 - индекс от 8 до 11)
Ошибка совпадения.

Шаг 8:
. *? = xxxxx - PASS (добавление символов в квантификатор неохоты). *? '. Cell 4 thru 8.)
foo = xfo - FAIL (ячейка 9,10,11 - индекс между 9 и 12)
Ошибка совпадения.

Шаг 9:
. *? = xxxxxx - PASS (добавьте символы в неохотный квантификатор). *? '. Cell 4 thru 9.)
foo = foo - PASS (ячейка 10,11,12 - индекс от 10 до 13)
Отчет MATCH

Шаг 10:
. *? = '' (пробел) - PASS (сравните как можно меньше с квантором неопределенности). *? '. Индекс 13 пуст.)
foo = Нет символа, оставшегося для соответствия - FAIL (Нет ничего, после того как индекс 13 будет соответствовать)
Ошибка совпадения.

Результат: 2 матча (ов)
Я нашел текст "xfoo", начинающийся с индекса 0 и заканчивающийся индексом 4.
Я нашел текст "xxxxxxfoo" начиная с индекса 4 и заканчивая индексом 13.

Possessive - сопоставить как можно больше с притяжательным квантилем и соответствовать всему регулярному выражению. НЕ отступайте.

Строка ввода: xfooxxxxxxfoo
Regex:. * + Foo

Вышеупомянутое регулярное выражение состоит из двух частей: '. * +' и 'foo'.

Шаг 1:
. * + = xfooxxxxxxfoo - PASS (Сопоставьте как можно больше с притяжательным квантификатором. * ')
foo = Отсутствует символ, который нужно совместить - FAIL (Ничего не следует после индекса 13)
Ошибка совпадения.

Примечание. Откат не допускается.

Результат: 0 совпадений

0

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

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

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

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

Признательная количественная оценка не имеет карантина и включает все в фиксированную активную последовательность.

0

Жадный: "соответствует максимально возможной последовательности символов"

Неохотно: "соответствовать кратчайшей последовательности символов"

Possessive: Это немного странно, поскольку он НЕ (в отличие от жадного и неохотного) пытается найти совпадение для всего регулярного выражения.

Кстати: Никакая реализация совпадения шаблонов регулярных выражений никогда не будет использовать обратное отслеживание. Все реалистичные шаблоны шаблонов чрезвычайно быстры - почти независимо от сложности регулярного выражения!

  • 0
    Насколько я знаю, большинство реализаций общего назначения в настоящее время упакованы настолько полно, что стало невозможным не использовать возврат. Поэтому в теории они должны быть чрезвычайно (экспоненциально) медленными в некоторых случаях. Но для большинства из этих случаев существуют специальные оптимизации, встроенные в сопоставитель шаблонов.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню