Евгений Колесников há 1 ano atrás
pai
commit
adac6517b6
1 ficheiros alterados com 129 adições e 0 exclusões
  1. 129 0
      articles/t5_regex.md

+ 129 - 0
articles/t5_regex.md

@@ -181,6 +181,135 @@ p><b>Tproger</b> — мой <i>любимый</i> сайт о программи
 
 Это происходит из-за того, что по умолчанию квантификатор работают по т.н. "жадному" алгоритму — старается вернуть как можно более длинную строку, соответствующую условию. Решить проблему можно двумя способами. Первый — использовать выражение `<[^>]*>`, которое запретит считать содержимым тега правую угловую скобку. Второй — объявить квантификатор не "жадным", а "ленивым". Делается это с помощью добавления справа к квантификатору символа "?". Т.е. для поиска всех тегов выражение обратится в `<.*?>`.
 
+## Опережающие и ретроспективные проверки
+
+В некоторых случаях нам нужно найти соответствия шаблону, но только те, за которыми или перед которыми следует другой шаблон.
+
+Для этого в регулярных выражениях есть специальный синтаксис: опережающая и ретроспективная проверка.
+
+В качестве первого примера найдём стоимость из строки `1 индейка стоит 30€`. То есть, найдём число, после которого есть знак валюты `€`.
+
+### Опережающая проверка
+
+Синтаксис опережающей проверки: `X(?=Y)`.
+
+Он означает: найди `X` при условии, что за ним следует `Y`. Вместо `X` и `Y` здесь может быть любой шаблон.
+
+Для целого числа, за которым идёт знак `€`, шаблон регулярного выражения будет `\d+(?=€)`:
+
+```js
+let str = "1 индейка стоит 30€";
+alert( str.match(/\d+(?=€)/) ); // 30, число 1 проигнорировано, так как за ним НЕ следует €
+```
+
+Обратим внимание, что проверка – это именно проверка, содержимое скобок `(?=...)` не включается в результат `30`.
+
+При поиске `X(?=Y)` движок регулярных выражений, найдя `X`, проверяет есть ли после него `Y`. Если это не так, то игнорирует совпадение и продолжает поиск дальше.
+
+Возможны и более сложные проверки, например `X(?=Y)(?=Z)` означает:
+
+1. Найти `X`.
+1. Проверить, идёт ли `Y` сразу после `X` (если нет – не подходит).
+1. Проверить, идёт ли `Z` сразу после `X` (если нет – не подходит).
+1. Если обе проверки прошли – совпадение найдено.
+
+То есть этот шаблон означает, что мы ищем `X` при условии, что за ним идёт и `Y` и `Z`.
+
+Такое возможно только при условии, что шаблоны `Y` и `Z` не являются взаимно исключающими.
+
+Например, `\d+(?=\s)(?=.*30)` ищет `\d+` при условии, что за ним идёт пробел, и где-то впереди есть `30`:
+
+```js
+let str = "1 индейка стоит 30€";
+
+alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1
+```
+
+В нашей строке это как раз число 1.
+
+### Негативная опережающая проверка
+
+Допустим, нам нужно узнать из этой же строки количество индеек, то есть число `\d+`, за которым НЕ следует знак `€`.
+
+Для этой задачи мы можем применить негативную опережающую проверку.
+
+Синтаксис: `X(?!Y)`
+
+Он означает: найди такой `X`, за которым НЕ следует `Y`.
+
+```js
+let str = "2 индейки стоят 60€";
+
+alert( str.match(/\d+(?!€)/) ); // 2 (в этот раз проигнорирована цена)
+```
+
+### Ретроспективная проверка
+
+Опережающие проверки позволяют задавать условия на то, что «идёт после».
+
+Ретроспективная проверка выполняет такую же функцию, но с просмотром назад. Другими словами, она находит соответствие шаблону, только если перед ним есть что-то заранее определённое.
+
+Синтаксис:
+
+* Позитивная ретроспективная проверка: `(?<=Y)X`, ищет совпадение с `X` при условии, что перед ним ЕСТЬ `Y`.
+* Негативная ретроспективная проверка: `(?<!Y)X`, ищет совпадение с `X` при условии, что перед ним НЕТ `Y`.
+
+Чтобы протестировать ретроспективную проверку, давайте поменяем валюту на доллары США. Знак доллара обычно ставится перед суммой денег, поэтому для того чтобы найти `$30`, мы используем `(?<=\$)\d+` – число, перед которым идёт `$`:
+
+```js
+let str = "1 индейка стоит $30";
+
+// знак доллара экранируем \$, так как это специальный символ
+alert( str.match(/(?<=\$)\d+/) ); // 30, одинокое число игнорируется
+```
+
+Если нам необходимо найти количество индеек – число, перед которым не идёт `$`, мы можем использовать негативную ретроспективную проверку `(?<!\$)\d+`:
+
+```js
+let str = "2 индейки стоят $60";
+
+alert( str.match(/(?<!\$)\d+/) ); // 2 (проигнорировалась цена)
+```
+
+### Скобочные группы
+
+Как правило, то что находится внутри скобок, задающих опережающую и ретроспективную проверку, не включается в результат совпадения.
+
+Например, в шаблоне `\d+(?=€)` знак `€` не будет включён в результат. Это логично, ведь мы ищем число `\d+`, а `(?=€)` – это всего лишь проверка, что за ним идёт знак `€`.
+
+Но в некоторых ситуациях нам может быть интересно захватить и то, что в проверке. Для этого нужно обернуть это в дополнительные скобки.
+
+В следующем примере знак валюты `(€|kr)` будет включён в результат вместе с суммой:
+
+```js
+let str = "1 индейка стоит 30€";
+let regexp = /\d+(?=(€|kr))/; // добавлены дополнительные скобки вокруг €|kr
+
+alert( str.match(regexp) ); // 30, €
+```
+
+То же самое можно применить к ретроспективной проверке:
+
+```js
+let str = "1 индейка стоит $30";
+let regexp = /(?<=(\$|£))\d+/;
+
+alert( str.match(regexp) ); // 30, $
+```
+
+### Итого
+
+Опережающая и ретроспективная проверки удобны, когда мы хотим искать шаблон по дополнительному условию на контекст, в котором он находится.
+
+Виды проверок:
+
+Шаблон | Тип | Совпадение
+:-----:|-----|-----------
+`X(?=Y)` | Позитивная опережающая | `X`, если за ним следует `Y`
+`X(?!Y)` | Негативная опережающая | `X`, если за ним НЕ следует `Y`
+`(?<=Y)X` | Позитивная ретроспективная | `X`, если следует за `Y`
+`(?<!Y)X` | Негативная ретроспективная | `X`, если НЕ следует за `Y`
+
 ## Регулярки в C#
 
 Основная функциональность регулярных выражений в .NET сосредоточена в пространстве имен *System.Text.RegularExpressions*. А центральным классом при работе с регулярными выражениями является класс **Regex**. Например, у нас есть некоторый текст и нам надо найти в нем все словоформы какого-нибудь слова. С классом **Regex** это сделать очень просто: