Предыдущая лекция | &nbsp; | Следующая лекция
:----------------:|:----------:|:----------------:
[Типы файлов: CSV, XML, JSON.](./t5_file_types.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [ООП. Базовые понятия.](./t6_oop1.md)

# Регулярные выражения

<!-- https://habr.com/ru/post/349860/ -->

<!-- 
https://github.com/kolei/OAP/blob/c2dc3a2c5c5c9d677f34030c4632a484ed94a568/articles/t3l6.md

старый вариант
 -->

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

>Термин «Регулярные выражения» является переводом английского словосочетания «Regular expressions». 

Регулярное выражение, или коротко «регулярка», состоит из обычных символов и специальных командных последовательностей. 

Например, **`\d`** задаёт любую цифру, а **`\d+`** — задает любую последовательность из одной или более цифр. 

Работа с регулярками реализована во всех современных языках программирования. 

Однако существует несколько «диалектов», поэтому функционал регулярных выражений может различаться от языка к языку (реализация регулярных выражений в C# совместима с PERL).

## Примеры регулярных выражений

Регулярка    | Её смысл
:-----------:|----------
simple text	 | В точности текст «simple text»
`\d{5}`    | Последовательности из 5 цифр<br/>`\d` - означает любую цифру<br/>`{5}` - ровно 5 раз
`\d\d/\d\d/\d{4}`	| Даты в формате ДД/ММ/ГГГГ<br/>(и прочие строки, на них похожие, например: 98/76/5432)
`\b\w{3}\b` | Слова в точности из трёх букв<br/>`\b` означает границу слова (с одной стороны буква, а с другой — нет)<br/>`\w` - любая буква,<br/>`{3}` - ровно три раза
`[-+]?\d+` |	Целое число, например, 7, +17, -42, 0013 (возможны ведущие нули)<br/>`[-+]?` - либо "-", либо "+", либо пусто<br/>`\d+` - последовательность из 1 или более цифр
`[-+]?(?:\d+(?:\.\d*)?`\|`\.\d+)(?:[eE][-+]?\d+)?` |	Действительное число, возможно в экспоненциальной записи<br/>Например, 0.2, +5.45, -.4, 6e23, -3.17E-14.

## Сила и ответственность

Регулярные выражения — это очень мощный инструмент. 

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

**Во-первых**, плохо написанные регулярные выражения работают медленно. 

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

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

Поэтому про регулярки часто говорят, что это *write only code* (код, который только пишут с нуля, но не читают и не правят).

Вот пример write-only регулярки (для проверки валидности e-mail адреса (не надо так делать!!!)):

```regexp
(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|
2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
```

А вот [здесь](http://www.ex-parrot.com/~pdw/Mail-RFC822-Address.html) более точная регулярка для проверки корректности email адреса стандарту RFC822. Если вдруг будете проверять email, то не делайте так! Если адрес вводит пользователь, то пусть вводит почти что угодно, лишь бы там были собака и точка. Надёжнее всего отправить туда письмо и убедиться, что пользователь может его получить.

## Документация и ссылки

* Оригинальная документация: https://docs.python.org/3/library/re.html;
* Очень подробный и обстоятельный материал: https://www.regular-expressions.info/;
* Разные сложные трюки и тонкости с примерами: http://www.rexegg.com/;
* Он-лайн отладка регулярок https://regex101.com;
* Он-лайн визуализация регулярок https://www.debuggex.com/

## Основы синтаксиса

Любая строка (в которой нет сециальных символов: `.^$*+?{}[]\|()`) сама по себе является регулярным выражением. Так, выражению "Хаха" будет соответствовать строка “Хаха” и только она. Регулярные выражения являются **регистрозависимыми**, поэтому строка “хаха” (с маленькой буквы) уже не будет соответствовать выражению выше. 

Регулярные выражения имеют спецсимволы `.^$*+?{}[]\|()`, которые в регулярках являются управляющими конструкциями. Для написания их просто как символов требуется их экранировать, для чего нужно поставить перед ними знак "**`\`**". Так же, как и везде, в регулярных выражения выражение **`\n`** соответствует концу строки, а **`\t`** — табуляции. 

### Шаблоны, соответствующие одному символу

Во всех примерах ниже соответствия регулярному выражению выделяются курсивом.

Шаблон | Описание | Пример | Применяем к тексту
:-----:|----------|:------:|-------------------
**.**	   | Один любой символ, кроме новой строки **`\n`** | м.л.ко | <u>м***о***л***о***ко</u><br/><u>м**а**л**а**ко</u><br/><u>м**0**л**0**ко</u>
**`\d`** | Любая цифра (Вообще говоря, в **`\d`** включается всё, что в **юникоде** помечено как «цифра») | СУ\d\d | <u>СУ**35**</u><br/><u>СУ**11**</u>1<br/>АЛ<u>СУ**14**</u>
**`\D`** | Любой символ, кроме цифры | `926\D123` | <u>926<b>)</b>123</u><br/>1<u>926<b>-</b>123</u>4
**`\s`** | Любой пробельный символ (пробел, табуляция, конец строки и т.п.) | `бор\sода` | <u>бор ода</u><br/>"<u>бор<br/>ода</u>"
**`\S`** | Любой непробельный символ | `\S123` | <u><b>X</b>123</u><br/><u><b>я</b>123</u><br/><u><b>!</b>123</u>456
**`\w`** | Любая буква (то, что может быть частью слова), а также цифры и знак "**_**" | `\w\w\w` | <u>**Год**</u><br/><u>**f_3**</u><br/><u>**qwe**</u>rt
**`\W`** | Любая не-буква, не-цифра и не подчёркивание | `сом\W` | <u>сом<b>!</b></u>, <u>сом<b>?</b></u>
**`[..]`** | Один из символов в скобках,<br/>а также любой символ из диапазона a-b | [0-9][0-9A-Fa-f] | <u>**12**</u><br/><u>**1F**</u><br/><u>**4B**</u>
**`[^..]`** | Любой символ, **кроме** перечисленных | \<[^\>]\> | <u><b>\<1\></b></u><br/><u><b>\<a\></b></u><br/>\<\>\>
[а-яА-ЯёЁ] | Буква “ё” не включается в общий диапазон букв (`\w`)! Поэтому для указания полного диапазона кириллицы приходится писать расширенный диапазон  
**`[abc-]`, `[-1]`** | если нужен знак минус, его нужно указать последним или первым (иначе он будет интерпретироваться как диапазон)
**`[(+\\\]]`** | внутри квадратных скобок нужно экранировать только закрывающую квадратную скобку "**]**" и знак "**`\`**"
**`\b`** | Начало или конец слова (слева пусто или не-буква, справа буква и наоборот).<br/>В отличие от предыдущих соответствует позиции, а не символу | `\bвал` | <u>**вал**</u><br/>перевал
**`\B`** | Не граница слова: либо и слева, и справа буквы, либо и слева, и справа НЕ буквы | `\Bвал` | пере<u>**вал**</u><br/>вал

### Квантификаторы (указание количества повторений)

Шаблон | Описание | Пример | Применяем к тексту
:-----:|----------|--------|-------------------
**`{n}`**| Ровно "n" повторений | `\d{4}` | 1<br/>12<br/>123<br/><u>**1234**</u><br/><u>**1234**</u>5
**`{m,n}`** | От "m" до "n" повторений включительно | `\d{2,4}` | 1<br/><u>**12**</u><br/><u>**123**</u><br/><u>**1234**</u><br/><u>**1234**</u>5
**`{m,}`** | Не менее "m" повторений | `\d{3,}` | 1<br/>12<br/><u>**123**</u><br/><u>**1234**</u><br/>...
**`{,n}`** | Не более "n" повторений | `\d{,2}` | <u>**1**</u><br/><u>**12**</u><br/><u>**12**</u>3
**`?`** | Ноль или одно вхождение, синоним `{0,1}` | `вал?` | <u>**вал**</u><br/><u>**валы**</u><br/><u>**вал**</u>ов
**`*`** | Ноль или более вхождений, синоним `{0,}` | `СУ\d*` | <u>**СУ**</u><br/><u>**СУ1**</u><br/><u>**СУ12**</u>...
**`+`** | Одно или более, синоним `{1,}` | `a\)+` | <u>**a)**</u><br/><u>**a))**</u><br/><u>**a)))**</u><br/>b<u>**a)**</u>])
__*?<br/>+?<br/>??<br/>{m,n}?<br/>{,n}?<br/>{m,}?__ |По умолчанию квантификаторы жадные - захватывают максимально возможное число символов. Добавление символа "?" делает их ленивыми, они захватывают минимально возможное число символов | `\(.*\)` | <u>**(a+b)\*(c+d)\*(e+f)**</u>
&nbsp;|&nbsp;| `\(.*?\)` | <u>**(a+b)**</u>\*(c+d)\*(e+f)

### Больше метасимволов

Есть некоторые метасимволы, которые мы еще не изучили. Большинство из них будут рассмотрены в этом разделе.

* "**`|`**"

    Соответствует оператору ИЛИ. Если А и В являются регулярными выражениями, то `A|B` будет соответствовать любая строка, которая соответствует А или В. Метасимвол "**|**" имеет очень низкий приоритет для того, чтобы заставить его работать разумно, когда вы чередуете несколько символов строки. `Crow|Servo` будет искать соответствие либо **Crow**, либо **Servo**, а не `Cro('w' или 'S')ervo`.

* "**`^`**"

    Ищет соответствие только в начале строки. Если включен флаг `MULTILINE`, как говорилось выше, то происходит сравнение и для каждой части после символа новой строки.

    Например, если вы хотите найти только те строки, у которых в начале имеется From, то в регулярном выражении записывается `^From`.

* "**`$`**"

    То же, что "**^**", но в конце строки, которая определяется либо, собственно по концу строки как таковому, либо по символу новой строки.

* **`\A`**

    Совпадение только в начале строки, то есть тоже, что "**^**", но не зависит от флага MULTILINE

* **`\Z`**

    Совпадение только в конце строки, то есть тоже, что "**$**", но не зависит от флага MULTILINE

* **`\b`**

    Граница слова. Слово определяется как последовательность символов чисел и/или букв, так что границы слова представляют пробелы или любые символы, не относящиеся к перечисленным.    

### Группировка

Часто необходимо получить больше информации, чем просто узнать, находит ли регулярное выражение соответствие или нет. Регулярные выражения часто используются для разрезания строк написанием регулярных выражений, разделенных на несколько подгрупп, которые соответствуют различным компонентам запроса. Например, в стандарте RFC-822 в заголовке имеются различные поля, разделенные двоеточием:

```
From: author@example.com
User-Agent: Thunderbird 1.5.0.9 (X11/20061227)
MIME-Version: 1.0
To: editor@example.com
```

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

Группы обозначаются метасимволами в виде круглых скобок '**`(`**', '**`)`**'. '**`(`**' и '**`)`**' имеют такой же смысл, как в математических выражениях; они группируют вместе выражения, содержащиеся в них, и вы можете повторять содержание группы повторяющими квалификаторами, такими как `*`, `+`, `?` и `{m, n}`. Например, `(ab)*` будет соответствовать нулю или более повторений `ab`.

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

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

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

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

Например, следующая "регулярка" обнаруживает в строке дважды подряд повторяющиеся слова:

```regexp
(\b\w+)\s+\1
```
### Ленивая квантификация

Предположим, перед нами стоит задача — найти все HTML-теги в строке

```html
<p><b>Tproger</b> — мой <i>любимый</i> сайт о программировании!</p>
```

Очевидное решение `<.*>` здесь не сработает — оно найдёт всю строку целиком, т.к. она начинается с тега абзаца (`<p>`) и им же заканчивается. То есть содержимым тега будет считаться строка:

```html
p><b>Tproger</b> — мой <i>любимый</i> сайт о программировании!</p
```

Это происходит из-за того, что по умолчанию квантификатор работают по т.н. "жадному" алгоритму — старается вернуть как можно более длинную строку, соответствующую условию. Решить проблему можно двумя способами. Первый — использовать выражение `<[^>]*>`, которое запретит считать содержимым тега правую угловую скобку. Второй — объявить квантификатор не "жадным", а "ленивым". Делается это с помощью добавления справа к квантификатору символа "?". Т.е. для поиска всех тегов выражение обратится в `<.*?>`.

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

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

Для этого в регулярных выражениях есть специальный синтаксис: опережающая и ретроспективная проверка.

В качестве первого примера найдём стоимость из строки `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** это сделать очень просто:

```cs
string s = "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа";
Regex regex = new Regex(@"туп(\w*)");
MatchCollection matches = regex.Matches(s);
if (matches.Count > 0)
{
    foreach (Match match in matches)
        Console.WriteLine(match.Value);
}
else
{
    Console.WriteLine("Совпадений не найдено");
}
```

Здесь мы находим в искомой строке все словоформы слова "туп". В конструктор объекта **Regex** передается регулярное выражение для поиска. Выражение `туп(\w*)` обозначает, найти все слова, которые имеют корень "туп" и после которого может стоять различное количество символов. Выражение `\w` означает алфавитно-цифровой символ, а звездочка после выражения указывает на неопределенное их количество - их может быть один, два, три или вообще не быть.

Метод *Matches* класса **Regex** принимает строку, к которой надо применить регулярные выражения, и возвращает коллекцию найденных совпадений.

Каждый элемент такой коллекции представляет объект **Match**. Его свойство *Value* возвращает найденное совпадение.

### Поиск с группами

Метод *Matches* ищет **все** вхождения словоформы *туп*, но на практике чаще приходится искать **одно** совпадение с шаблоном, но разбитое на группы. Для этого больше подходит метод *Match*, который возвращает объект типа **Match**. У этого объекта есть свойство *Success*, по которому мы можем определить найдена ли строка по шаблону и массив *Groups*, в котором находятся значения подгрупп (в группе 0, напоминаю, находится вся найденная строка).

```cs
string text = "One car red car blue car";
string pat = @"(\w+)\s+(car)";

Regex r = new Regex(pat, RegexOptions.IgnoreCase);

Match m = r.Match(text);
int matchCount = 0;
// можно искать не одно вхождение, а несколько
while (m.Success)
{
    Console.WriteLine("Match"+ (++matchCount));
    // тут можно было бы перебирать по длине массива Groups, 
    // но мы по своему шаблону и так знаем, что у нас две подгруппы
    for (int i = 1; i <= 2; i++)
    {
    Console.WriteLine($"Group {i}='{m.Groups[i]}'");
    }
    // поиск следующей подстроки соответсвующей шаблону
    m = m.NextMatch();
}
```

Выведет

```
Match1
Group 1=One
Group 2=car
Match2
Group 1=red
Group 2=car
Match3
Group 1=blue
Group 2=car
```

### Параметр RegexOptions

Класс **Regex** имеет ряд конструкторов, позволяющих выполнить начальную инициализацию объекта. Две версии конструкторов в качестве одного из параметров принимают перечисление *RegexOptions*. Некоторые из значений, принимаемых данным перечислением:

* **Compiled**: при установке этого значения регулярное выражение компилируется в сборку, что обеспечивает более быстрое выполнение

* **CultureInvariant**: при установке этого значения будут игнорироваться региональные различия

* **IgnoreCase**: при установке этого значения будет игнорироваться регистр

* **IgnorePatternWhitespace**: удаляет из строки пробелы и разрешает комментарии, начинающиеся со знака #

* **Multiline**: указывает, что текст надо рассматривать в многострочном режиме. При таком режиме символы "^" и "$" совпадают, соответственно, с началом и концом любой строки, а не с началом и концом всего текста

* **RightToLeft**: приписывает читать строку справа налево

* **Singleline**: устанавливает однострочный режим, а весь текст рассматривается как одна строка

Например:

```cs
Regex regex = new Regex(
    @"туп(\w*)", 
    RegexOptions.IgnoreCase);
```

При необходимости можно установить несколько параметров:

```cs
Regex regex = new Regex(
    @"туп(\w*)", 
    RegexOptions.Compiled | RegexOptions.IgnoreCase);
```

Теперь посмотрим на некоторые примеры использования. Возьмем первый пример с скороговоркой "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа" и найдем в ней все слова, где встречается корень "губ":

```cs
string s = "Бык тупогуб, тупогубенький бычок, у быка губа бела была тупа";
Regex regex = new Regex(@"\w*губ\w*");
```

Так как выражение `\w*` соответствует любой последовательности алфавитно-цифровых символов любой длины, то данное выражение найдет все слова, содержащие корень "губ".

Второй простенький пример - нахождение телефонного номера в формате `111-111-1111`:

```cs
string s = "456-435-2318";
Regex regex = new Regex(
    @"\d{3}-\d{3}-\d{4}");
```

Если мы точно знаем, сколько определенных символов должно быть, то мы можем явным образом указать их количество в фигурных скобках: `\d{3}` - то есть в данном случае три цифры.

Мы можем не только задать поиск по определенным типам символов - пробелы, цифры, но и задать конкретные символы, которые должны входить в регулярное выражение. Например, перепишем пример с номером телефона и явно укажем, какие символы там должны быть:

```cs
string s = "456-435-2318";
Regex regex = new Regex(
    "[0-9]{3}-[0-9]{3}-[0-9]{4}");
```

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

Также можно задать диапазон для алфавитных символов: `Regex regex = new Regex("[a-v]{5}");` - данное выражение будет соответствовать любому сочетанию пяти символов, в котором все символы находятся в диапазоне от `a` до `v`.

Можно также указать отдельные значения: `Regex regex = new Regex(@"[2]*-[0-9]{3}-\d{4}");`. Это выражение будет соответствовать, например, такому номеру телефона "222-222-2222" (так как первые числа двойки)

С помощью операции `|` можно задать альтернативные символы: `Regex regex = new Regex(@"[2|3]{3}-[0-9]{3}-\d{4}");`. То есть первые три цифры могут содержать только двойки или тройки. Такой шаблон будет соответствовать, например, строкам "222-222-2222" и "323-435-2318". А вот строка "235-435-2318" уже не подпадает под шаблон, так как одной из трех первых цифр является цифра 5.

Итак, у нас такие символы, как `*`, `+` и ряд других используются в качестве специальных символов. И возникает вопрос, а что делать, если нам надо найти, строки, где содержится точка, звездочка или какой-то другой специальный символ? В этом случае нам надо просто экранировать эти символы слешем:

```cs
Regex regex = new Regex(
    @"[2|3]{3}\.[0-9]{3}\.\d{4}");
// этому выражению будет соответствовать строка "222.222.2222"
```

### Проверка на соответствие строки формату

Нередко возникает задача проверить корректность данных, введенных пользователем. Это может быть проверка электронного адреса, номера телефона, Класс **Regex** предоставляет статический метод *IsMatch*, который позволяет проверить входную строку с шаблоном на соответствие:

```cs
string pattern = @"^(?("")(""[^""]+?""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
                @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9]{2,17}))$";
while (true)
{
    Console.WriteLine("Введите адрес электронной почты");
    string email = Console.ReadLine();
 
    if (Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase))
    {
        Console.WriteLine("Email подтвержден");
        break;
    }
    else
    {
        Console.WriteLine("Некорректный email");
    }
}
```

Переменная *pattern* задает регулярное выражение для проверки адреса электронной почты. Данное выражение предлагает нам Microsoft на страницах msdn.

Для проверки соответствия строки шаблону используется метод IsMatch: `Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase)`. Последний параметр указывает, что регистр можно игнорировать. И если введенная строка соответствует шаблону, то метод возвращает **true**.

### Замена и метод Replace

Класс **Regex** имеет метод *Replace*, который позволяет заменить строку, соответствующую регулярному выражению, другой строкой:

```cs
string s = "Мама  мыла  раму. ";
string pattern = @"\s+";
string target = " ";
Regex regex = new Regex(pattern);
string result = regex.Replace(s, target);
```

Данная версия метода *Replace* принимает два параметра: строку с текстом, где надо выполнить замену, и сама строка замены. Так как в качестве шаблона выбрано выражение "\s+ (то есть наличие одного и более пробелов), метод Replace проходит по всему тексту и заменяет несколько подряд идущих пробелов ординарными.

---

## Задание на дом:

1. Реализовать примеры из лекции.

1. Лабораторную по теме "строки" реализовать через регулярки (где это возможно)

Предыдущая лекция | &nbsp; | Следующая лекция
:----------------:|:----------:|:----------------:
[Типы файлов: CSV, XML, JSON.](./t5_file_types.md) | [Содержание](../readme.md#тема-5-продвинутый-c-функции-лямбды-исключения-работа-с-файлами-многопоточность-регулярные-выражения) | [ООП. Базовые понятия.](./t6_oop1.md)
