Почему на минимальное (не жадное) совпадение влияет символ конца строки '$'?

РЕДАКТИРОВАТЬ: удалить исходный пример, потому что он спровоцировал вспомогательные ответы. также исправил заголовок.

Вопрос в том, почему наличие «$» в регулярном выражении влияет на жадность выражения:

Вот более простой пример:

>>> import re
>>> str = "baaaaaaaa"
>>> m = re.search(r"a+$", str)
>>> m.group()
'aaaaaaaa'
>>> m = re.search(r"a+?$", str)
>>> m.group()
'aaaaaaaa'

"?" вроде ничего не делает. Обратите внимание, что когда «$» удаляется, тогда «?» уважается:

>>> m = re.search(r"a+?", str)
>>> m.group()
'a'

РЕДАКТИРОВАТЬ: Другими словами, "a+?$" соответствует ВСЕМ a вместо только последнего, это не то, что я ожидал. Вот описание регулярного выражения "+?" из документов Python: "Добавление '?' после того, как квалификатор заставит его выполнить сопоставление нежадным или минимальным образом; будет сопоставлено как можно меньше символов».

В данном примере это не так: строка «a» соответствует регулярному выражению «a+?$», так почему же совпадение того же регулярного выражения в строке «baaaaaaa» не является просто одной a (крайний правый один)?


person krumpelstiltskin    schedule 03.05.2011    source источник
comment
Не могли бы вы немного пояснить свой вопрос? Мне трудно понять, чего именно вы хотите. Что вы имеете в виду под первым матчем? Вы говорите о .+?   -  person arussell84    schedule 04.05.2011
comment
Может быть лучший способ сделать это с другой библиотекой (в контексте путей), но в основном это вопрос о регулярных выражениях.   -  person krumpelstiltskin    schedule 04.05.2011
comment
Под первым совпадением я подразумеваю первый поиск(), я его отредактирую.   -  person krumpelstiltskin    schedule 04.05.2011
comment
@krumpelstiltskin Тогда хорошо. Я сказал это в своем ответе ниже, но это потому, что вы поместили все в скобки, поэтому все было помещено в группу. За скобками нет ничего, что соответствовало бы остальной части строки.   -  person arussell84    schedule 04.05.2011
comment
@arussell84 arussell84 я добавил второй пример, который проясняет проблему. ваш ответ ниже не решает проблему.   -  person krumpelstiltskin    schedule 04.05.2011
comment
@krumpelstiltskin снова отредактировал мой ответ.   -  person arussell84    schedule 04.05.2011


Ответы (6)


Совпадения «упорядочены» по "самый левый, затем самый длинный"; однако «самый длинный» - это термин, использовавшийся до того, как был разрешен нежадный режим, и вместо этого он означает что-то вроде «предпочтительного количества повторений для каждого атома». Быть крайним слева более важно, чем количество повторений. Таким образом, «a+?$» не будет соответствовать последней букве «А» в «baaaaa», поскольку сопоставление первой буквы «А» начинается раньше в строке.

(Ответ изменен после уточнения OP в комментариях. См. История для предыдущего текста.)

person Fred Nurk    schedule 04.05.2011
comment
@krumpelstiltskin: Это была ошибка копирования-вставки. Первоначально я использовал совпадение вместо поиска, перечитал ваш вопрос, затем добавил «c» и забыл заменить два из них. - person Fred Nurk; 04.05.2011
comment
Я не думаю, что это объясняет, почему search() не учитывает '?' в моем примере выше. - person krumpelstiltskin; 04.05.2011
comment
@krumpelstiltskin: Да, но, возможно, новый абзац выше объяснит это лучше. - person Fred Nurk; 04.05.2011
comment
@Fred Nurk: но почему в моем примере это не соответствует последней букве? ни один из ваших примеров не соответствует этой возможности. - person krumpelstiltskin; 04.05.2011
comment
@krumpelstiltskin: А, это проясняет ситуацию. Это просто так не работает. Самые левые совпадения считаются более важными, чем более длинные (что в данном случае означает более короткие, поскольку вы переключаетесь с жадных на нежадные) совпадения. - person Fred Nurk; 04.05.2011
comment
@Fred Nurk: это ответ, который я хотел. Не могли бы вы поместить ссылку на очень хороший документ, который @Alan Moore дает нам, говорящий нам, что Python - это язык, ориентированный на регулярные выражения, и что он стремится найти совпадение (самый левый)? - person krumpelstiltskin; 06.05.2011

Нежадный модификатор влияет только на то место, где совпадение останавливается, и никогда не влияет на то место, где оно начинается. Если вы хотите начать матч как можно позже, вам придется добавить .+? в начало вашего шаблона.

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

РЕДАКТИРОВАТЬ:

Подробнее... В этом случае:

re.search(r"a+?$", "baaaaaaaa")

движок регулярных выражений будет игнорировать все до первой буквы «а», потому что так работает re.search. Он будет соответствовать первому a и «захочет» вернуть совпадение, за исключением того, что он еще не соответствует шаблону, потому что он должен достичь совпадения для $. Так что он просто ест a по одной и проверяет $. Если бы он был жадным, он не проверял бы $ после каждого a, а только после того, как не смог бы найти больше a.

Но в этом случае:

re.search(r"a+?", "baaaaaaaa")

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

person Mu Mind    schedule 04.05.2011
comment
Это лучший ответ, который я видел, поскольку это описание того, что происходит. Хотя это не объясняет, почему это происходит. - person krumpelstiltskin; 04.05.2011

Наличие $ в регулярном выражении не влияет на жадность выражения. Он просто добавляет еще одно условие, которое должно быть выполнено для успешного совпадения в целом.

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

С помощью a+$ и a+?$ вы добавили еще одно условие: совпадение хотя бы с одним a, за которым следует конец строки. a+ сначала потребляет все a, а затем переходит к якорю ($). Это удается с первой попытки, поэтому a+ не требуется возвращать какие-либо из своих a.

С другой стороны, a+? изначально потребляет только один a, прежде чем передать его $. Это не удается, поэтому управление возвращается к a+?, который потребляет еще один a и снова переключается. И так до тех пор, пока a+? не поглотит последний a, а $, наконец, не добьется успеха. Так что да, a+?$ соответствует тому же количеству a, что и a+$, но делает это неохотно, а не жадно.

Что касается самого длинного правила слева, которое упоминалось в другом месте, оно никогда не применялось к производным от Perl разновидностям регулярных выражений, таким как Python. Даже без неохотных квантификаторов они всегда могут вернуть меньшее, чем максимальное соответствие благодаря упорядоченному чередованию. Я думаю, Ян понял правильную идею: производные от Perl (или ориентированные на регулярные выражения) варианты должны называться eager, не жадный.

Я считаю, что самое длинное правило слева применяется только к регулярным выражениям POSIX NFA, которые используют механизмы NFA под капотом, но должны возвращать те же результаты, что и регулярное выражение DFA (текстовое).

person Alan Moore    schedule 05.05.2011
comment
Вот и все! Самое длинное правило слева является причиной. Однако этот ответ многословен. Есть ли шанс, что вы могли бы сократить его, просто чтобы сказать, что он соответствует самой длинной совпадающей подстроке, которая является самой левой. Есть ли ссылка на какую-то документацию по python, в которой говорится об этом? - person krumpelstiltskin; 06.05.2011
comment
Я нашел похожую тему, в которой говорится об этой ситуации, но в ответе нет ссылки на соответствующую документацию: здесь - person krumpelstiltskin; 06.05.2011
comment
Вот именно: движок, ориентированный на регулярные выражения, такой как Python, не выдерживает самое длинное совпадение. Он берет первое найденное совпадение, даже если из той же начальной точки были доступны более длинные совпадения. И все это не является исключительным для Python. PHP, Java, .NET: все популярные разновидности Perl работают одинаково. Вы перешли по второй ссылке в моем ответе? Ян хорошо объясняет эти проблемы здесь: regular-expressions.info/engine.html - person Alan Moore; 06.05.2011

Ответ на исходный вопрос:

Почему первый search() охватывает несколько «/», а не кратчайшее совпадение?

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

Ответ на измененный вопрос:

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

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

person John Machin    schedule 04.05.2011
comment
Этот ответ устарел, так как я упростил пример. - person krumpelstiltskin; 04.05.2011
comment
@krumpelstiltskin: единственная устаревшая часть — это цитата из вашего первоначального вопроса. Остальная часть по существу дает правильный ответ на оба вопроса, однако я обновлю его. - person John Machin; 04.05.2011
comment
@Johh Machin: но весь шаблон следует за +?$ только в последнем символе a строки baaaaaaaa. - person krumpelstiltskin; 04.05.2011
comment
@krumpelstiltskin: Точно. Этот процесс продолжается до тех пор, пока (False или весь шаблон не совпадет). Я не понимаю твоего но. - person John Machin; 04.05.2011
comment
вы говорите, что изначально будет выполнено кратчайшее возможное совпадение. Однако, если это приведет к сбою всего шаблона, он будет повторен с дополнительным символом. В моем примере кратчайшее возможное совпадение не приводит к сбою всего шаблона: весь шаблон представляет собой +?$, который без проблем подходит подстроке a из str. - person krumpelstiltskin; 05.05.2011

Здесь есть две проблемы. Вы использовали group() без указания группы, и я могу говорят, что вы запутались между поведением регулярных выражений с группой, явно заключенной в скобки, и без группы, заключенной в скобки. Это поведение без круглых скобок, которое вы наблюдаете, — это просто ярлык, который предоставляет Python, и вам нужно прочитать документацию на group(), чтобы понять его полностью.

>>> import re
>>> string = "baaa"
>>> 
>>> # Here you're searching for one or more `a`s until the end of the line.
>>> pattern = re.search(r"a+$", string)
>>> pattern.group()
'aaa'
>>> 
>>> # This means the same thing as above, since the presence of the `$`
>>> # cancels out any meaning that the `?` might have.
>>> pattern = re.search(r"a+?$", string)
>>> pattern.group()
'aaa'
>>> 
>>> # Here you remove the `$`, so it matches the least amount of `a` it can.
>>> pattern = re.search(r"a+?", string)
>>> pattern.group()
'a'

Суть в том, что строка a+? соответствует одному a, точка. Однако a+?$ совпадает с a до конца строки. Обратите внимание, что без явной группировки вам будет трудно заставить ? вообще что-либо означать. В общем, в любом случае лучше четко указать, что вы группируете с помощью круглых скобок. Позвольте мне привести вам пример с явными группами.

>>> # This is close to the example pattern with `a+?$` and therefore `a+$`.
>>> # It matches `a`s until the end of the line. Again the `?` can't do anything.
>>> pattern = re.search(r"(a+?)$", string)
>>> pattern.group(1)
'aaa'
>>>
>>> # In order to get the `?` to work, you need something else in your pattern
>>> # and outside your group that can be matched that will allow the selection
>>> # of `a`s to be lazy. # In this case, the `.*` is greedy and will gobble up
>>> # everything that the lazy `a+?` doesn't want to.
>>> pattern = re.search(r"(a+?).*$", string)
>>> pattern.group(1)
'a'

Изменить: удален текст, относящийся к старым версиям вопроса.

person arussell84    schedule 03.05.2011
comment
ну... на самом деле мне нужна только начальная часть, поэтому я использую re.sub(), чтобы избавиться от совпадающей подстроки. - person krumpelstiltskin; 04.05.2011
comment
вы видели, как меняется размер совпадения при включении/исключении $? это любопытный момент. - person krumpelstiltskin; 04.05.2011
comment
@krumpelstiltskin: посмотри мой ответ - person John Machin; 04.05.2011
comment
@arussell84: см. мои комментарии Фреду Нурку. - person krumpelstiltskin; 05.05.2011
comment
@krumpelstiltskin Вы имеете в виду, что не понимаете, почему ? не уважают в a+?$? Это потому, что $ важнее. Как я уже сказал, a+?$ означает соответствие a до конца строки ($). На самом деле, ни в одном из ваших примеров ? не имеет значения. - person arussell84; 05.05.2011
comment
@ arussell84 ясно, что '?' ничего не делает... на самом деле, мой вопрос, почему это не так? см. мое редактирование в вопросе выше для более подробной информации и цитаты из документации по python. - person krumpelstiltskin; 06.05.2011
comment
@krumpelstiltskin На этой странице вам дали ответ на ваш вопрос в разных формах. Если вы еще этого не понимаете, я не знаю, как вам помочь. Вам нужен пример того, где ? работает или что-то в этом роде? - person arussell84; 06.05.2011
comment
@krumpelstiltskin Я снова отредактировал свой ответ, чтобы удалить материалы, относящиеся к более старым версиям вашего вопроса. Я понял, что у меня уже есть пример того, как заставить ? работать. По сути, ? внутри группы ленив только тогда, когда это разрешено, и вам нужно что-то еще за пределами вашей группы, чтобы соответствовать остальной части строки, которая вам не нужна в вашей группе. - person arussell84; 06.05.2011

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

>>> import os
>>> p = "/we/shant/see/this/butshouldseethis"
>>> os.path.basename(p)
butshouldseethis
person jonesy    schedule 03.05.2011