Странное поведение отрицательного взгляда вперед

У меня есть следующая строка: "text before AB000CD000CD text after". Я хочу сопоставить текст из AB с первым вхождением CD. Вдохновленный этим ответом, я создал следующий шаблон регулярного выражения:

AB((?!CD).)*CD

Я проверил результат в https://regex101.com/ и получил следующий результат:

Full match  12-19   `AB000CD`
Group 1.    16-17   `0`

Похоже, он делает то, что мне нужно. Однако я не понимаю, почему это работает. Насколько я понимаю, мой шаблон должен сначала соответствовать AB, затем любому символу, за которым не следует CD, а затем сам CD. Но следуя этой логике, результат не должен включать 000, а только 00, потому что за последним нулем на самом деле следует CD. Мое объяснение неверно?


person username    schedule 11.07.2017    source источник
comment
Вы были близки, это должно быть AB((?:(?!CD).)*)CD, группа захвата должна заключать внутреннюю группу квантификации кластера (?:(?!CD).)*. Такой способ позволяет захватывать весь контент между AB и CD.   -  person    schedule 12.07.2017
comment
the result should not include 000, but only 00 because the last zero is actually followed by CD. Is my explanation wrong Да, это неправильно. Скажем, текущая позиция здесь-› 0CD, 0C != CD, поэтому 0 потребляется. Тогда это здесь-› CD. Поскольку CD == CD это не удается, затем переходит к следующей части регулярного выражения, где оно соответствует CD.   -  person    schedule 12.07.2017


Ответы (1)


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

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

См. справку по rexegg.com о том, когда его использовать:

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

{START}(?:(?!{MID}).)*?{END}

Если нужно избегать большего количества фраз, мы просто добавляем их в нашу закаленную точку:

{START}(?:(?!{MID})(?!{RESTART}).)*?{END}

Кроме того, см. этот поток < /а>.

person Wiktor Stribiżew    schedule 11.07.2017
comment
На самом деле есть причины предпочесть ((?!CD).)*CD (.*?)CD. Например, ((?!CD).)*CD никогда не будет соответствовать CD в части ((?!CD).)*, в то время как (.*?)CD может совпадать, если поиск отменяется. Это не имеет значения, если в регулярном выражении после CD ничего нет, как здесь, но это может иметь значение для других регулярных выражений. - person user2357112 supports Monica; 12.07.2017
comment
@user2357112 user2357112 Возможно, мне следует добавить еще одну цитату с сайта rexegg.com, Когда не следует использовать эту технику: Для рассматриваемой задачи эта техника не дает никаких преимуществ перед ленивой точкой-звездой .*?{END}. Хотя их логика различается, на каждом этапе, перед сопоставлением символа, оба метода заставляют движок проверять, является ли то, что следует за {END}. Под рукой было регулярное выражение {START}(?:(?!{END}).)*{END}, аналогичное регулярному выражению OP. - person Wiktor Stribiżew; 12.07.2017