Регулярные выражения сопоставляются слева направо и перемещают своего рода «курсор» вдоль строки по мере их прохождения. Если ваше регулярное выражение содержит обычный символ, такой как a
, это означает: «если перед курсором стоит буква a
, переместите курсор вперед на один символ и продолжайте движение. В противном случае что-то не так; сделайте резервную копию и попробуйте что-нибудь другое». Таким образом, вы можете сказать, что a
имеет «ширину» в один символ.
«Утверждение нулевой ширины» — это просто: оно утверждает что-то о строке (т. е. не соответствует, если какое-то условие не выполняется), но не перемещает курсор вперед, потому что его "ширина" равна нулю.
Вы, вероятно, уже знакомы с некоторыми более простыми утверждениями нулевой ширины, такими как ^
и $
. Они соответствуют началу и концу строки. Если курсор не находится в начале или в конце, когда он увидит эти символы, механизм регулярных выражений завершится ошибкой, создаст резервную копию и попытается сделать что-то еще. Но на самом деле они не перемещают курсор вперед, потому что не соответствуют символам; они только проверяют, где находится курсор.
Lookahead и lookbehind работают одинаково. Когда механизм регулярных выражений пытается сопоставить их, он проверяет вокруг курсора, чтобы увидеть, находится ли правильный шаблон впереди или позади него, но в случае совпадения он не перемещает курсор.
Рассмотреть возможность:
/(?=foo)foo/.match 'foo'
Это будет соответствовать! Механизм регулярных выражений выглядит следующим образом:
- Начните с начала строки:
|foo
.
- Первая часть регулярного выражения —
(?=foo)
. Это означает: совпадение, только если foo
появляется после курсора. Имеет ли это? Ну да, так что мы можем продолжить. Но курсор не двигается, потому что это нулевая ширина. У нас еще есть |foo
.
- Далее идет
f
. Есть ли f
перед курсором? Да, продолжайте и переместите курсор за f
: f|oo
.
- Далее идет
o
. Есть ли o
перед курсором? Да, продолжайте и переместите курсор за o
: fo|o
.
- То же самое снова, приводя нас к
foo|
.
- Мы достигли конца регулярного выражения, и ничего не пошло не так, поэтому шаблон совпадает.
В частности, по вашим четырем утверждениям:
(?=...)
— это «прогноз»; он утверждает, что ...
действительно появляется после курсора.
1.9.3p125 :002 > 'jump june'.gsub(/ju(?=m)/, 'slu')
=> "slump june"
«Ju» в слове «jump» совпадает, потому что «m» следует за ним. Но рядом с «дзю» в «июне» нет «м», поэтому его не трогают.
Поскольку он не перемещает курсор, вы должны быть осторожны, помещая что-либо после него. (?=a)b
никогда не будет соответствовать чему-либо, потому что он проверяет, что следующий символ равен a
, а затем также проверяет, что тот же символ равен b
, что невозможно.
(?<=...)
- это "обратный просмотр"; он утверждает, что ...
действительно появляется перед курсором.
1.9.3p125 :002 > 'four flour'.gsub(/(?<=f)our/, 'ive')
=> "five flour"
«Наш» в «четырех» соответствует, потому что непосредственно перед ним стоит «f», но «наш» в «муке» имеет непосредственно перед ним «л», поэтому оно не совпадает.
Как и выше, вы должны быть осторожны с тем, что ставите перед. a(?<=b)
никогда не будет совпадать, потому что он проверяет, что следующий символ — a
, перемещает курсор, а затем проверяет, что предыдущий символ был b
.
(?!...)
- "отрицательный прогноз"; он утверждает, что ...
не появляется после курсора.
1.9.3p125 :003 > 'child children'.gsub(/child(?!ren)/, 'kid')
=> "kid children"
"ребенок" соответствует, потому что дальше идет пробел, а не "ren". "дети" - нет.
Это, вероятно, тот, из которого я получаю больше всего пользы; Точный контроль того, что не может произойти дальше, пригодится.
(?<!...)
- "отрицательный просмотр назад"; он утверждает, что ...
не появляется перед курсором.
1.9.3p125 :004 > 'foot root'.gsub(/(?<!r)oot/, 'eet')
=> "feet root"
«oot» в «foot» подходит, так как перед ним нет «r». «oot» в «root» явно имеет «r».
В качестве дополнительного ограничения большинство движков регулярных выражений требуют, чтобы в этом случае ...
имело фиксированную длину. Таким образом, вы не можете использовать ?
, +
, *
или {n,m}
.
Вы также можете вкладывать их друг в друга и делать всевозможные сумасшедшие вещи. Я использую их в основном для одноразовых работ, которые, как я знаю, мне никогда не придется поддерживать, поэтому у меня нет под рукой хороших примеров реальных приложений; честно говоря, они достаточно странные, поэтому вы должны сначала попытаться сделать то, что хотите, каким-то другим способом. :)
Запоздалая мысль: синтаксис взят из регулярных выражений Perl, которые использовали (?
с последующими различными символами для многих расширенный синтаксис, потому что ?
сам по себе недействителен. Так что <=
само по себе ничего не значит; (?<=
— это один токен целиком, означающий «это начало просмотра назад». Это похоже на то, как +=
и ++
являются отдельными операторами, хотя оба они начинаются с +
.
Однако их легко запомнить: =
означает смотреть вперед (или, точнее, «здесь»), <
означает смотреть назад, а !
имеет традиционное значение «не».
Что касается ваших более поздних примеров:
irb(main):002:0> "foresight".sub(/(?!s)ight/, 'ee')
=> "foresee"
irb(main):003:0> "foresight".sub(/ight/, 'ee')
=> "foresee"
Да, они производят тот же результат. Это сложный момент с использованием просмотра вперед:
- Механизм регулярных выражений пробовал некоторые вещи, но они не работали, и теперь это
fores|ight
.
- Он проверяет
(?!s)
. Символ после курсора s
? Нет, это i
! Итак, эта часть совпадает, и сопоставление продолжается, но курсор не двигается, и у нас все еще есть fores|ight
.
- Он проверяет
ight
. ight
идет после курсора? Ну да, так и есть, так что двигайте курсором: foresight|
.
- Были сделаны!
Курсор переместился на подстроку ight
, так что это полное совпадение, и это то, что заменяется.
Делать (?!a)b
бесполезно, так как вы говорите: следующий символ не должен быть a
, а должен быть b
. Но это то же самое, что просто сопоставить b
!
Иногда это может быть полезно, но вам нужен более сложный шаблон: например, (?!3)\d
будет соответствовать любой цифре, отличной от 3.
Это то, что вы хотите:
1.9.3p125 :001 > "foresight".sub(/(?<!s)ight/, 'ee')
=> "foresight"
Это утверждает, что s
не предшествует раньше ight
.
person
Eevee
schedule
17.01.2013