Почему регулярные выражения жадны по умолчанию?

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

Это только по устаревшим причинам (так было сначала сделано, и каждая реализация копирует это), или для этого есть причина?


person Yishai    schedule 16.02.2010    source источник
comment
Кто бы ни проголосовал за закрытие как субъективное и спорное, не поленитесь уточнить?   -  person falstro    schedule 16.02.2010
comment
Регулярные выражения не являются жадными по умолчанию, но их квантификаторы :-)   -  person Andy E    schedule 16.02.2010
comment
Мне кажется, настоящий вопрос заключается в том, почему ленивые квантификаторы хуже поддерживаются и/или неудобны в использовании, чем жадные?   -  person Ipsquiggle    schedule 16.02.2010
comment
этот вопрос также беспокоил меня, логически думая, что создание ленивого механизма регулярных выражений намного эффективнее и проще, чем жадный, они должны были сделать режим по умолчанию ленивым, потому что это воспринимается по умолчанию. Кроме того, во всей моей жизни с использованием регулярных выражений я не помню, чтобы использовал жадное совпадение более 1% времени.   -  person shabby    schedule 16.01.2014


Ответы (6)


Истеричный Райсенс


Часть ответа может включать происхождение RE в практических вычислениях. Первоначально они были теоретической концепцией из теории автоматов и теории формального языка до Кен Томпсон сам написал настоящую реализацию и использовал ее в qed и ed( 1).

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

person DigitalRoss    schedule 16.02.2010
comment
Я не уверен, что вы можете сказать, что теоретические обычные языки по умолчанию являются жадными. Я думаю, что регулярное выражение Клини определяет набор всех строк, которые могут ему соответствовать, поэтому /x*/ может соответствовать или x, или xxx (и т. д.). Такое выражение определяет обычный язык, включающий строки , x и xxx. Обратите внимание, что здесь ничего не говорится о том, как искать совпадения в тексте; только когда вы применяете теорию, вы начинаете заботиться о жадности. - person Nate C-K; 16.02.2010
comment
Конечно, конечно, под исходной версией я просто имел в виду в том виде, в каком Кен Томпсон напечатал ее для этих редакторов, и в этих версиях, и почти десятилетие спустя, ed, grep, ex , а vi выполняет только жадное сопоставление с образцом. - person DigitalRoss; 16.02.2010
comment
Ах, в таком случае мы согласны. - person Nate C-K; 17.02.2010

В случае производительности ленивые квантификаторы не всегда быстрее из-за поиска с возвратом: http://blog.stevenlevithan.com/archives/greedy-lazy-performance

Что касается фактического дизайна, я, честно говоря, не могу сказать, почему квантификаторы по умолчанию жадные, но мне интересно, какой управляющий символ использовался бы, чтобы сделать квантификатор жадным, а не ленивым. Я не думаю, что ? обрезал бы его :-)

person Andy E    schedule 16.02.2010
comment
@forefinger: Разве это не соответствует концу строки/строки? - person Andy E; 17.02.2010
comment
Оно делает. Это просто кажется лучшим жадным символом. - person forefinger; 17.02.2010

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

person kennytm    schedule 16.02.2010
comment
@roe: Да, оба поведения квантификатора могут потребовать возврата. - person Gumbo; 16.02.2010

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

Что касается того, должен ли типичный вариант использования быть нежадным, как насчет следующего: предположим, у меня есть файл с такими записями, как foo1909, bar3939, baz3331, и я просто хочу извлечь эти числа. Кажется достаточно естественным написать (\d*) в качестве регулярного выражения для этого.

Вы можете сказать, что так же просто написать (\d*)\D или что-то еще, но в основном всегда так, что программист может быть более явным и менее двусмысленным. Поскольку мы хотели, чтобы поведение по умолчанию было на 100% предсказуемым и тривиальным для расчета в уме, мне это кажется разумным.

person forefinger    schedule 16.02.2010
comment
Это совершенно логичная и разумная догадка, однако она совершенно не связана с реальной причиной, которая заключается просто в том, что нежадность появилась намного, намного позже, и поэтому она не была по умолчанию. - person DigitalRoss; 16.02.2010

Настоящей проблемой здесь является оператор замыкания Клини (звезда); для всего остального в регулярном выражении самое длинное совпадение совпадает с самым коротким совпадением.

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

  • И ksh, и bash обеспечивают формы "наибольшего совпадения" и "кратчайшего совпадения" для большинства специальных операторов, изменяющих переменные.

  • Регулярные выражения Lua включают * для самого длинного совпадения замыкания Клини и - для кратчайшего совпадения замыкания Клини. Это всегда кусает меня, когда я забываю избежать буквального знака -.

Было бы интересно вернуться к оригинальной работе Клини и посмотреть, не повлияло ли это на ранние инструменты на самое длинное совпадение.

person Norman Ramsey    schedule 17.02.2010
comment
А чередование? Учитывая регулярное выражение /foo|foobar/ и целевую строку blahfoobarblah, математически чистое регулярное выражение всегда будет соответствовать foobar, в то время как регулярное выражение NFA, полученное из Perl, будет соответствовать foo. - person Alan Moore; 17.02.2010

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

Я хочу прояснить, что это неправильно, если только «типичный вариант использования» не означает взлом HTML.

Простой пример — лексические анализаторы для языков программирования. Вы просто не хотите

foo = 42

интерпретируется как 3 переменные, за которыми следует знак равенства, за которым следуют 2 числа. Наоборот, обычно вы ожидаете, что ваш синтаксический анализатор будет рассматривать максимально длинные совпадения.

До появления HTML мы, старшие, десятилетиями жили с жадными регулярными выражениями, и у нас это прекрасно получалось. Даже сегодня я не использую нежадные в 99% случаев, по общему признанию, потому что мне лень смотреть синтаксис, но также и потому, что редко бывают случаи, когда вы не можете просто написать хорошо завершенный жадный. Например, чтобы сопоставить строку:

"(\\"|[^"])*"
person Ingo    schedule 12.02.2013
comment
Я не думаю, что лексические анализаторы многого добились бы, если бы искали что-то, а также просто следили за тем, чтобы все остальное не было чем-то. Конечно, это выглядит прилично для строк, которые разделены одним символом, но вы пытаетесь сделать это для чего-то другого, разделенного несколькими символами, и это быстро становится безобразным, например, многострочный комментарий: /\*((?!\*/).)*\*/. Против /\*.*?\*/. Чем больше вы добавляете разделителей, тем хуже, потому что вам приходится отрицать их все. Жадный работает для большинства случаев использования, потому что эти проблемы возникают нечасто, но утверждение, что это приносит пользу лексическому анализу, или иным образом неверно... - person TrisT; 21.06.2021