Опережающие и ретроспективные проверки в регулярных выражениях

  Владислав Титов  | 

  Обновл. 25 Ноя 2020  | 

 4771

На этом уроке мы рассмотрим группирование, обратные ссылки, опережающие и ретроспективные проверки, и, как всегда, проиллюстрируем это всё на практических примерах.

Группирование

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

Допустим, нам нужно узнать, упоминается ли какой-то конкретный человек. Его могут звать Джон Реджинальд Смит, но второе имя может присутствовать, а может и нет:

Регулярное выражение: John (Reginald )?Smith
Пример: John Reginald Smith is sometime just called John Smith.

Совет: Обратите внимание на пробелы в регулярном выражении, приведенном выше. Важно помнить, что они являются частью вашего регулярного выражения, и вы должны убедиться, что они находятся в нужных местах.

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

Регулярное выражение: John (Reginald)? Smith
Пример: Проблема с этим регулярным выражением заключается в том, что оно найдет соответствие John Reginald Smith и даже John  Smith (два пробела между John и Smith), но не найдет соответствие в John Smith. Можете ли вы понять, почему?

Вы не ограничены только обычными символами в скобках. Вы можете использовать в скобках и метасимволы (включая мультипликаторы).

Например, нам нужно найти экземпляры IP-адресов. IP-адрес представляет собой набор из 4-х чисел (от 0 до 255), разделенных точками (например, 192.168.0.5):

Регулярное выражение: \b(\d{1,3}\.){3}\d{1,3}\b
Пример: The server has an address of 10.18.0.20 and the printer has an address of 10.18.0.116.

Это уже немного сложнее, поэтому давайте разберемся детально:

   \b обозначает границу слова;

   здесь мы разбили IP-адрес на 3 части, состоящие из чисел от 0 до 255 + точки + последнее (4-е) число от 0 до 255;

   в скобках мы обрабатываем первые 3 части IP-адреса: \d{1,3} указывает на то, что мы ищем от 1 до 3 цифр + точку, следующую за этими цифрами, которую мы не забываем экранировать (\.). Мы ищем ровно 3 таких последовательности, поэтому указываем мультипликатор {3} сразу за скобками;

   наконец, мы ищем 4-е число, указывая \d{1,3}, и обозначаем конечную границу слова (\b).

Обратные ссылки


Всякий раз, когда мы сопоставляем что-либо в круглых скобках, это значение фактически сохраняется в переменной, к которой мы можем обратиться позже в регулярном выражении. Для доступа к этой переменной мы используем escape-символ (\), за которым следует цифра. Первый набор скобок обозначается как \1, второй — \2 и т.д.

Допустим, нам нужно найти строки с двумя упоминаниями о человеке с фамилией Smith. При этом, мы не знаем его имени. Мы могли бы сделать следующее:

Регулярное выражение: (\b[A-Z]\w+\b) Smith.*\1 Smith
Пример: Harold Smith went to meet John Smith but John Smith was not there.

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

Чередование и оператор ИЛИ

При чередовании мы имеем возможность искать что-либо из указанного нами списка. Мы видели очень простой пример чередования с использованием оператора диапазона. Это позволяет нам выполнить поиск одного символа, но иногда нам может быть нужно выполнить операцию и с большим набором символов. Это делается с помощью оператора ИЛИ (|). Например, нам нужно найти все экземпляры dog или cat:

Регулярное выражение: dog|cat
Пример: Harold Smith has two dogs and one cat.

Мы также можем использовать несколько операторов ИЛИ для использования большего количества вариантов:

Регулярное выражение: dog|cat|bird
Пример: Harold Smith has two dogs, one cat and three birds.

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

Регулярное выражение: (John|Harold) Smith
Пример: Harold Smith went to meet John Smith but instead bumped into Jane Smith.

Опережающие и ретроспективные проверки


Опережающие и ретроспективные проверки работают в одном из двух режимов:

   позитивный — мы пытаемся найти что-то соответствующее;

   негативный — мы пытаемся найти что-то несоответствующее.

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

Опережающая проверка

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

Например, допустим, что нам нужно идентифицировать числа больше 4000, но меньше 5000. Эта задачка выглядит на первый взгляд простой, но на самом деле она чуть сложнее. Обычно, первым решением, которое приходит на ум, является:

Регулярное выражение: \b4\d\d\d\b
Пример: This looks promising with 4021 but unfortunately also matches 4000.

Теперь мы понимаем, что нам нужно искать число 4, за которым следуют 3 цифры, и хотя бы одна из этих цифр не должна быть нулем. Мы можем попробовать сделать следующее:

Регулярное выражение: \b4([1-9]\d\d|\d[1-9]\d|\d\d[1-9])\b
Пример: Now we will match 4010 but not 4000.

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

Теперь вы можете подумать, глядя на всё вышеизложенное, что регулярное выражение для всего лишь 4-х цифр получилось слишком громоздким. И подумайте о том, как оно увеличится, если вместо диапазона от 4000 до 5000 нам нужно будет искать совпадения в диапазоне от 40000 до 50000. Вы быстро убедитесь, что вышеприведенное регулярное выражение работает, но оно не элегантно и не масштабируется.

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

Синтаксис негативной опережающей проверки:

(?!x)

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

Таким образом, мы можем решить вышеприведенную задачу следующим образом:

Регулярное выражение: \b4(?!000)\d\d\d\b
Пример: Now we still match 4010 but not 4000.

Это может показаться немного запутанным, поэтому давайте разберемся детально:

   сначала мы ищем цифру 4;

   когда мы находим 4, негативная опережающая проверка возвращает true, если следующие 3 символа не являются 000;

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

Говоря простым языком, расшифруем регулярное выражение, приведенное выше, как: «Мы ищем 4, за которым не следует 3 нуля, но следуют 3 другие цифры».

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

Синтаксис позитивной опережающей проверки:

(?=x)

Всё, что нам нужно сделать, — это заменить ! на =.

Ретроспективная проверка


Ретроспективные проверки работают так же, как и опережающие проверки, но вместо того, чтобы смотреть вперед, а затем отбрасывать результат, мы смотрим назад, и затем отбрасываем результат. Подобно опережающим проверкам, ретроспективные проверки также могут быть как позитивными, так и негативными. Они используют аналогичный синтаксис, но с добавлением < после ?.

Синтаксис позитивной ретроспективной проверки:

(?<=x)

Синтаксис негативной ретроспективной проверки:

(?<!x)

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

Регулярное выражение: (?<=[A-Z]\w* )Smith
Пример: Now we won't identify Smith Francis but we will identify Harold Smith.

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

Заключение

Резюмируем то, что мы узнали:

   () — группируем часть регулярного выражения;

   \1, \2 и т.д. — ссылаемся на соответствие, найденное группой ранее;

   | — оператор ИЛИ: ищем соответствие либо правому, либо левому операнду;

   (?=x) — позитивная опережающая проверка;

   (?!x) — негативная опережающая проверка;

   (?<=x) — позитивная ретроспективная проверка;

   (?<!x) — негативная ретроспективная проверка.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (14 оценок, среднее: 4,86 из 5)
Загрузка...

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *