Как концепция Look-ahead и Look-behind поддерживает такую ​​​​концепцию утверждений нулевой ширины в Regex Ruby?

Я только что рассмотрел концепцию Zero-Width Assertions из документации. И мне приходят в голову несколько быстрых вопросов:

  • почему такое имя Zero-Width Assertions?
  • Как концепция Look-ahead и look-behind поддерживает такую ​​концепцию Zero-Width Assertions?
  • Что такое ?<=s,<!s,=s,<=s - 4 символа инструктируют внутри паттерна? не могли бы вы помочь мне сосредоточиться, чтобы понять, что на самом деле происходит

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

irb(main):001:0> "foresight".sub(/(?!s)ight/, 'ee')
=> "foresee"
irb(main):002:0> "foresight".sub(/(?=s)ight/, 'ee')
=> "foresight"
irb(main):003:0> "foresight".sub(/(?<=s)ight/, 'ee')
=> "foresee"
irb(main):004:0> "foresight".sub(/(?<!s)ight/, 'ee')
=> "foresight"

Может ли кто-нибудь помочь мне понять здесь?

ИЗМЕНИТЬ

Здесь я попробовал два фрагмента кода, один с концепциями "утверждений нулевой ширины", как показано ниже:

irb(main):002:0> "foresight".sub(/(?!s)ight/, 'ee')
=> "foresee"

а другой без концепций "утверждений нулевой ширины", как показано ниже:

irb(main):003:0> "foresight".sub(/ight/, 'ee')
=> "foresee"

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

Спасибо


person Arup Rakshit    schedule 17.01.2013    source источник


Ответы (3)


Регулярные выражения сопоставляются слева направо и перемещают своего рода «курсор» вдоль строки по мере их прохождения. Если ваше регулярное выражение содержит обычный символ, такой как a, это означает: «если перед курсором стоит буква a, переместите курсор вперед на один символ и продолжайте движение. В противном случае что-то не так; сделайте резервную копию и попробуйте что-нибудь другое». Таким образом, вы можете сказать, что a имеет «ширину» в один символ.

«Утверждение нулевой ширины» — это просто: оно утверждает что-то о строке (т. е. не соответствует, если какое-то условие не выполняется), но не перемещает курсор вперед, потому что его "ширина" равна нулю.

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

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

Рассмотреть возможность:

/(?=foo)foo/.match 'foo'

Это будет соответствовать! Механизм регулярных выражений выглядит следующим образом:

  1. Начните с начала строки: |foo.
  2. Первая часть регулярного выражения — (?=foo). Это означает: совпадение, только если foo появляется после курсора. Имеет ли это? Ну да, так что мы можем продолжить. Но курсор не двигается, потому что это нулевая ширина. У нас еще есть |foo.
  3. Далее идет f. Есть ли f перед курсором? Да, продолжайте и переместите курсор за f: f|oo.
  4. Далее идет o. Есть ли o перед курсором? Да, продолжайте и переместите курсор за o: fo|o.
  5. То же самое снова, приводя нас к foo|.
  6. Мы достигли конца регулярного выражения, и ничего не пошло не так, поэтому шаблон совпадает.

В частности, по вашим четырем утверждениям:

  • (?=...) — это «прогноз»; он утверждает, что ... действительно появляется после курсора.

    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"

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

  1. Механизм регулярных выражений пробовал некоторые вещи, но они не работали, и теперь это fores|ight.
  2. Он проверяет (?!s). Символ после курсора s? Нет, это i! Итак, эта часть совпадает, и сопоставление продолжается, но курсор не двигается, и у нас все еще есть fores|ight.
  3. Он проверяет ight. ight идет после курсора? Ну да, так и есть, так что двигайте курсором: foresight|.
  4. Были сделаны!

Курсор переместился на подстроку 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
comment
Вы также не можете сопоставлять (произвольно) вложенные скобки с обходами. - person Tim Pietzcker; 18.01.2013
comment
о, да, это использование рекурсии захвата. я придумаю лучший пример - person Eevee; 18.01.2013
comment
это столько объяснений, сколько я могу придумать! пожалуйста, скажите мне, если вам нужно что-нибудь еще - person Eevee; 18.01.2013
comment
Вы, люди, дали мне много. Это мой справочник для пересмотра в любое время этой концепции. Все в одном месте! :) Хорошая вещь. Каждый должен взять концепцию начать отсюда! Сногсшибательно......... Большое вам спасибо, друзья. Остается ответить только на мой последний EDIT, что даст нам еще один вкус, если кто-нибудь ответит на это :) :) - person Arup Rakshit; 18.01.2013
comment
Идеальный! больше нет вопросов! Хочу быть твоей ученицей, дорогая :) - person Arup Rakshit; 18.01.2013

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

Когда вы видите строку foo, вы, естественно, читаете три символа. Но есть также четыре позиции, отмеченные здесь вертикальной чертой: |f|o|o|. Упреждающий или опережающий просмотр (также известный как просмотр назад) соответствует позиции, в которой символ до или после соответствует выражению.

Разница между выражением нулевой ширины и другими выражениями заключается в том, что выражение нулевой ширины соответствует (или использует) только позицию. Так, например:

/(app)apple/

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

/(?=app)apple/

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

ОБЗОРНЫЕ ОПИСАНИЯ

Положительный прогноз: (?=s)

Представьте, что вы сержант-инструктор и проводите инспекцию. Вы начинаете в начале очереди с намерением пройти мимо каждого рядового и убедиться, что он соответствует ожиданиям. Но прежде чем сделать это, вы смотрите вперед один за другим, чтобы убедиться, что они выстроились в порядке свойств. Имена рядовых: A, B, C, D и E. /(?=ABCDE)...../.match('ABCDE'). Да, они все присутствуют и учтены.

Отрицательный прогноз: (?!s)

Вы выполняете инспекцию по цепочке и, наконец, стоите у рядового D. Теперь вы собираетесь посмотреть вперед, чтобы убедиться, что F из другой роты еще раз случайно не соскользнул в неправильное построение. /.....(?!F)/.match('ABCDE'). Нет, в этот раз он не проскользнул, так что все в порядке.

Положительный анализ: (?<=s)

После завершения осмотра сержант находится в конце строя. Он поворачивается и оглядывается назад, чтобы убедиться, что никто не ускользнул. /.....(?<=ABCDE)/.match('ABCDE'). Да, все присутствуют и учтены.

Отрицательный ретроспективный анализ: (?<!s)

Наконец, сержант-инструктор бросает последний взгляд, чтобы убедиться, что рядовые А и Б снова не поменялись местами (потому что им нравится КП). /.....(?<!BACDE)/.match('ABCDE'). Нет, их нет, так что все в порядке.

person JDB still remembers Monica    schedule 17.01.2013
comment
МОЙ БОГ! идеально - это то, о чем я спрашивал вас, люди, более четкое, чем онлайн-материалы, которые я просматривал до сих пор. Вы видели мою последнюю EDIT? - person Arup Rakshit; 18.01.2013
comment
В вашем посте я понял, что на самом деле означает Zero-width в Ruby Regex? и фактор consuming - то, что действительно является хорошей подпиткой для ученика Regexp. - person Arup Rakshit; 18.01.2013

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

"foresight".sub(/sight/, 'ee')

то, что соответствует

foresight
    ^^^^^

и, таким образом, результат будет

foreee

Однако в этом примере

"foresight".sub(/(?<=s)ight/, 'ee')

то, что соответствует

foresight
     ^^^^

и поэтому результат будет

foresee

Другим примером утверждения нулевой ширины является символ границы слова, \b. Например, чтобы найти полное слово, вы можете попробовать окружить его пробелами, например,

"flight light plight".sub(/\slight\s/, 'dark')

получить

flightdarkplight

Но вы видите, как сопоставление пробелов удаляет его во время подстановки? Использование границы слова позволяет обойти эту проблему:

"flight light plight".sub(/\blight\b/, 'dark')

\b соответствует началу или концу слова, но на самом деле не соответствует символу: это нулевая ширина.

Возможно, самый краткий ответ на ваш вопрос таков: упреждающие и ретроспективные утверждения — это один из видов утверждений нулевой ширины. Все утверждения просмотра вперед и назад являются утверждениями нулевой ширины.


Вот пояснения к вашим примерам:

irb(main):001:0> "foresight".sub(/(?!s)ight/, 'ee')
=> "foresee"

Выше вы говорите: «Сопоставьте, где следующий символ не s, а затем i». Это всегда верно для i, так как i никогда не бывает s, поэтому замена выполняется успешно.

irb(main):002:0> "foresight".sub(/(?=s)ight/, 'ee')
=> "foresight"

Выше вы говорите: «Сопоставьте, где следующий символ — это s, а затем i». Это никогда не верно, поскольку i никогда не бывает s, поэтому подстановка не выполняется.

irb(main):003:0> "foresight".sub(/(?<=s)ight/, 'ee')
=> "foresee"

Выше уже объяснили. (Это правильный вариант.)

irb(main):004:0> "foresight".sub(/(?<!s)ight/, 'ee')
=> "foresight"

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

person slackwing    schedule 17.01.2013
comment
consumes zero characters - что потребляет, не могли бы вы объяснить технически? Как Look-ahead и Look-behind происходят технически и помогают генерировать такой вывод с концепцией zero-width assertion, пожалуйста, покажите мне. Я жажду увидеть вещи! - person Arup Rakshit; 18.01.2013
comment
Я думаю, что @Eevee объяснил это лучше всего. Я добавил несколько примеров в свой ответ, чтобы помочь вам в дальнейшем. - person slackwing; 18.01.2013