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

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

  Обновл. 13 Окт 2019  | 

 1006

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

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

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

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

Регулярное выражение: 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.

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


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

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

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

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

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

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

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

Регулярное выражение: \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.

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

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

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

(?!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 (4 оценок, среднее: 5,00 из 5)
Загрузка...

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

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