Regex for lazy developers. Регулярные выражения для ленивых)

ARTICLES 20.11.21 20.11.21 324
Бесплатные курсына главную сниппетов

Регулярные выражения – это система обработки текста, основанная на специальной системе записи образцов. Проще говоря представляет программистам возможность легко обрабатывать и валидировать строки. Представляет имплементацию принципа DRY (Don’t Repeat Yourself), почти во всех поддерживаемых языках паттерн регулярных выражений не будет изменятся от слова совсем. Код написанный на backend и frontend приложениях будет идентичным, тем самым позволяя экономить время командам на реализацию одинаковых фич. Также стоит акцентировать внимание на том, что данный модуль идеально подходит для работы с большими строками или сложными строками т.к. даёт возможность решать задачи связанные с ними просто и быстро. Бывает за чашечкой чая на кухне или в команде вы можете услышать, что регулярные выражения довольно сложные в изучение, написание и чтение и вообще их придумали ужасные люди 😈. Но так ли это? Давайте разбираться

Примечание:

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

Как это выглядит

Далее представлены примеры на 6-х языках программирования определения российского номера телефона.

Паттерн

^(?:8|+7)[0-9]{10}$

C#

new Regex(“^(?:8|+7)[0-9]{10}$”).Match(“testdata”)

PHP

preg_match(“/^(?:8|+7)[0-9]{10}$/”, “testdata”, match[])

Python

re.match(r’^(?:8|+7)[0-9]{10}$’,’testdata’)

JS

/^(?:8|+7)[0-9]{10}$/.exec(“testdata”)

Java

“testdata”.matches(“^(?:8|+7)[0-9]{10}$”)

Dart

RegExp(r"^(?:8|+7)[0-9]{10}$").allMatches(“testdata”)

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

История появления

Впервые регулярные выражения возникли в научных работах по теории автоматов и теории формальных языков в середине 1950-х. Стефан Коул Клин признан человеком, который впервые ввёл понятие Регулярных Выражений.

Принципы и идеи, заложенные в его работах, были практически реализованы Кеном Томпсоном и с его лёгкой руки проникли в язык Perl.

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

Язык Регулярных Выражений не является полноценным языком программирования, хотя, как и другие языки, имеет свой синтаксис и команды.

Какие языки программирования поддерживают их?

Список довольно обширный вот лишь несколько из них:

Языки программирования поддерживающие regex

Возможности

Где пригодится?

Базовый синтаксис

Упрощение синтаксиса

Разбор базового синтаксиса

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

Позитивный результат

Негативный результат

(Ч|ч)асы

“Часы”, “часы”

“39”, “Уаз”, “Час”, “чсы”, “асы”

(Ч|ч)?асы

“Часы”, “часы”, “асы”

“39”, “Уаз”, “Час”, “чсы”

.асы

“Часы”, “уасы”, “#асы”, “Zасы”, “4асы”

“39”, “Уаз”, “Час”, “чсы”, “асы”

[0-9]+

“62”, “1”, “5123”, “632”

“abs”

a+b

“ab”, “aab”, “aaaaaaab”

“abs”, “b”, “cb” 

[0-9]*

“62”, “1”, “5123”, “632”

“abs”

a*b

“ab”, “aab”, “aaaaaaab”, “b”

“abs”

\+7

“+7”

“+1”, “++7”, “-7”

^\+7\d{10}

“+79991112233”, “+712345678900”

“+712345678”

^\+7\d{10}$

“+79991112233” 

“+712345678900”, “+712345678”

Длина условий

Кроме валидации значений в строке мы также можем указывать сколько символов должно проходить одно и тоже условие. Есть всего три возможности работать длиной условий:

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

Позитивный результат

Негативный результат

[0-9] {3}

“632”, “777”, “891”

“39”, “1”, “5123”, “abs”

[0-9] {3,5}

“632”, “12345”, “5123”

“39”, “7”, “125123”, “abs”

[0-9] {3,}

“632”, “123456”, “5123”

“39”, “0”, “abs”

Примечание: Условие “[0-9]” можно заменить на сокращение “\d”

Работа с группами (Advanced)

Дальше будет немного сложно, готовьтесь.

Реальный пример из жизни

Однажды по работе нужно было парсить данные с QR кода, которые печатались на чеках при покупке/возврате разных товаров, услуг и т.д. Первая версия парсера была написана на бекенд части (C#). Кодовая база парсера была объёмом в ~150 строк кода, она не учитывала некоторые особенности разных фискальных регистраторов (устройства, которые печатают чеки и отправляют данные в ФНС). Для изменения данной функции требовалось внимательно смотреть, проверять каждую строчку кода. Позже вариантов стало так много и появилась необходимость использовать её на фронте для валидации. Соответственно, было решено переписать её, используя регулярные выражения для упрощение парсера и возможности легкого и быстрого переноса его на другой язык программирования.

Цели: 

  1. Парсить входные значения для валидации по шаблону

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

  3. Проверять что поле “n” равно всегда 1 (0 – возврат, 1 – покупка)

Входные данные:

t=20181125T142800&s=850.12&fn=8715000100011785&i=86841&fp=1440325305&n=1

Регулярное выражение на парсинг данных:

^t=(?<Date>[0-9-:T]+)&s=(?<Sum>[0-9]+(?:\.[0-9]{2})?)&fn=[0-9]+&i=[0-9]+&fp=[0-9]+&n=1$

Пример кода (C#):

private static (string date, string sum) parseQRCode(string data)
    {
    var pattern = new Regex(@"^t=(?<Date>[0-9-:T]+)&s=(?<Sum>[0-9]+(?:\.[0-9]{2})?)&fn=[0-9]+&i=[0-9]+&fp=[0-9]+&n=1$", RegexOptions.ECMAScript);
    var matchResult = pattern.Match(data);
    if (!matchResult.Success)
    throw new ArgumentException("Invalid qrCode");
    var dateGroup = matchResult.Groups["Date"];
    if(!dateGroup.Success)
    throw new ArgumentException("Invalid qrCode, Date group not found");
    var sumGroup = matchResult.Groups["Sum"];
    if(!sumGroup.Success)
    throw new ArgumentException("Invalid qrCode, Sum group not found");

    return (dateGroup.Value, sumGroup.Value);
    }

Пример кода на JS:

Это вариант сделан через Exception-ы, но можно сделать через return false или return null.

const parseQRCode = (data:string) : {date: string, sum: string} => {
    const pattern = new RegExp("^t=(?<Date>[0-9-:T]+)&s=(?<Sum>[0-9]+(?:\.[0-9]{2})?)&fn=[0-9]+&i=[0-9]+&fp=[0-9]+&n=1$");
    const matchResult = pattern.exec(data);
    if (!matchResult)
    throw "Invalid qrCode";
    const dateGroup = matchResult[1];
    if(!dateGroup)
    throw "Invalid qrCode, Date group not found";
    const sumGroup = matchResult[2];
    if(!sumGroup)
    throw "Invalid qrCode, Sum group not found";
    return {date: dateGroup, sum: sumGroup};
    };

На выходе мы получаем получаем два значения:

  1. Date – поле обозначающее дату и время покупки (осталось только спарсить её и превратить в объект даты)

  2. Sum – сумма покупки

Теперь давайте разберём паттерн поподробнее:

  1. ^ – обозначающая начало строки

  2. t=(?<Date>[0-9-:T]+) – обязательные символы  t=(далее любые символы (от 0 до 9 или - или : или T) в одном или более экземпляре)

  3. &s=(?<Sum>[0-9]+(?:\.[0-9]{2})?) – обязательные символы 

    1. &s= – обязательная последовательность символов & и s и =

    2. [0-9]+(символы от 0 до 9 одном или более экземпляре)

    3. (?:\.[0-9]{2})?

  4. $ – обозначающая конец строки

  5. &fn=[0-9]+ – обязательные символы &fn= и далее [0-9]+ -> (любое число от 0 до 9 в одном или более экземпляре)

  6. &i=[0-9]+ – обязательные символы &i= и далее [0-9]+ -> (любое число от 0 до 9 в одном или более экземпляре)

  7. &fp=[0-9]+ – обязательные символы &fp= и далее [0-9]+ -> (любое число от 0 до 9 в одном или более экземпляре)

  8. &n=1 – обязательные символы &n=1

Проблема работы с не латынью

Когда вам необходимо работать со всем алфавитом из латыни, достаточно просто написать [a-zA-Z]. Многие подумают что при работе с кириллицей хватает написать [а-яА-Я]. Вроде всё логично и всё хорошо, но в какой-то момент вы поймете, что у вас иногда она работает некорректно. Проблема заключается в том, что диапазон [а-я] не включает в себя букву “ё”, соответственно, вам необходимо изменить ваш паттерн с [а-яА-Я] на [а-яёА-ЯË], чтобы код учитывал специфичную букву в алфавите. Такая проблема есть не только в кириллице, также эта проблема актуальна для греческого, турецкого и ряда других языков. Будьте внимательны при написание паттерна, который должен использовать эти языки.

Флаги regex в JS

Дополнительные настройки regex в C#

RegexOptions выставляется как дополнительный параметр в конструкторе Regex класса. Также его можно указывать в методах Match, Matches.

Хорошие практики и советы по оптимизации

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

  2. Используя сокращения (\d, \w и другие), будьте уверены что они полностью соответствуют вашим условиям поиска.

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

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

  5. Старайтесь уменьшить количество экранирования (\), данный функционал замедляет скорость выполнения во многих языках программирования.

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

Заключение

Регулярные выражения лишь хотят казаться сложными, но на деле возможности которые они предоставляют дают массу возможностей и позволяют упростить и ускорить работу всем от Junior-а до Senior/Lead-a. Пожалуйста, если у вас будут вопросы милости прошу в комментарии, где мы сможем с вами подискутировать.

P.S. Программируйте это всё ещё классно)

P.P.S. Привет всем кто пришёл сюда с mergeconf

P.P.P.S. Спасибо Serge78rus за небольшое исправление по описанию операции +.

Ссылки

Замеры на разных языках

→ Онлайн regex-помощник со словарём всех доступных команд и поддержкой нескольких языков программирования.

 

Регулярные выражения. 10 минут на урокРегулярные выражения. 10 минут на урок 175 страниц · 2005 · 22.06 MB · русский by Бен Форта
Регулярные выраженияРегулярные выражения 598 страниц · 2008 · 12.58 MB · русский by Джеффри Фридл
Регулярные выражения. Сборник рецептовРегулярные выражения. Сборник рецептов 607 страниц · 2012 · 4.2 MB · русский by Ян Гойвертс & С. Левитан
Регулярные выражения. ОсновыРегулярные выражения. Основы 144 страницы · 2015 · 12.76 MB · русский by Майкл Фицджеральд
 
на главную сниппетов
Курсы