Регулярні вирази (regex) - потужний інструмент для роботи з рядками тексту (англ. string) в Elixir. Регулярні вирази в Elixir відповідають специфікації PCRE (Perl Compatible Regular Expressions). Рядкові шаблони, які виражають значення регулярного виразу, спочатку компілюються, а потім використовуються для пошуку збігів у всьому рядку тексту або в його частині.
В Elixir найпоширеніший спосіб створити регулярний вираз - скористатися сигілом ~r. Сигіли надають скорочення у вигляді синтаксичного цукру для поширених завдань в Elixir. Щоб знайти збіг із літералом рядка тексту, можна використати сам рядок як шаблон після сигіла.
~r/test/
Оператор =~/2 стане в пригоді, щоб перевірити рядок тексту на збіг із регулярним виразом і повернути результат типу boolean.
"this is a test" =~ ~r/test/
# => true
Дві зауваги щодо використання сигілів:
/
\)Зіставлення діапазону символів за допомогою квадратних дужок [] задає клас символів. Він збігається з будь-яким одним символом із тих, що входять до класу. Можна також указати діапазон символів, наприклад a-z, за умови, що його початок і кінець утворюють неперервний діапазон кодових точок.
regex = ~r/[a-z][ADKZ][0-9][!?]/
"jZ5!" =~ regex
# => true
"jB5?" =~ regex
# => false
Скорочені класи символів роблять шаблон компактнішим. Наприклад:
\d - скорочення для [0-9] (будь-яка цифра)\w - скорочення для [A-Za-z0-9_] (будь-який «словесний» символ)\s - скорочення для [ \t\r\n\f] (будь-який пробільний символ)Коли скорочений клас символів використовується поза сигілом, його потрібно екранувати: "\\d"
Альтернації використовують | як спеціальний символ, що позначає збіг з одним або іншим варіантом
regex = ~r/cat|bat/
"bat" =~ regex
# => true
"cat" =~ regex
# => true
Квантифікатори дозволяють задати повторюваний шаблон у регулярному виразі. Вони впливають на групу, що стоїть перед квантифікатором.
{N, M}, де N - мінімальна кількість повторень, а M - максимальна{N,} - відповідає N або більшій кількості повторень
{0,} можна також записати як *: відповідає нулю або більшій кількості повторень{1,} можна також записати як +: відповідає одному або більшій кількості повторень{,N} - відповідає до N повтореньКруглі дужки () використовуються, щоб позначити групи та захоплення. У деяких випадках групу також можна захопити, щоб повернути її для подальшого використання. В Elixir такі захоплення можуть бути іменованими або неіменованими. Захопленням дають імена, дописуючи ?<name> після відкритої дужки. Групи працюють як єдине ціле, наприклад, коли за ними йдуть квантифікатори.
regex = ~r/(h)at/
Regex.replace(regex, "hat", "\\1op")
# => "hop"
regex = ~r/(?<letter_b>b)/
Regex.scan(regex, "blueberry", capture: :all_names)
# => [["b"], ["b"]]
Якорі привʼязують регулярний вираз до початку або кінця рядка тексту, у якому шукаємо збіг:
^ привʼязує до початку рядка тексту$ привʼязує до кінця рядка текстуОскільки ~r - це скорочення для "pattern" |> Regex.escape() |> Regex.compile!(), можна також скористатися інтерполяцією рядків, щоб динамічно побудувати шаблон регулярного виразу:
anchor = "$"
regex = ~r/end of the line#{anchor}/
"end of the line?" =~ regex
# => false
"end of the line" =~ regex
# => true
Наше завдання - написати сервіс, який приймає події. Кожна подія має повʼязану з нею дату, але ми помічаємо, що на ендпойнт нашого сервісу надходять три різні формати:
"01/01/1970""January 1, 1970""Thursday, January 1, 1970"Ми бачимо, що між ними є спільні риси, і вирішуємо написати кілька шаблонів регулярних виразів, які можна поєднувати.
Реалізуйте day/0, month/0 і year/0, щоб вони повертали рядок тексту (англ. string) зі шаблоном, який після компіляції зіставлявся б з числовими частинами в "01/01/1970" (dd/mm/yyyy). День і місяць можуть бути записані як 1 або 01 (доповнені нулями зліва).
"31" =~ DateParser.day() |> Regex.compile!()
# => true
"12" =~ DateParser.month() |> Regex.compile!()
# => true
"1970" =~ DateParser.year() |> Regex.compile!()
# => true
Реалізуйте day_names/0 і month_names/0, щоб вони повертали рядок тексту зі шаблоном, який після компіляції зіставлявся б відповідно з назвою дня тижня та назвою місяця року.
"Tuesday" =~ DateParser.day_names() |> Regex.compile!()
# => true
"June" =~ DateParser.month_names() |> Regex.compile!()
# => true
Реалізуйте capture_day/0, capture_month/0, capture_year/0, capture_day_name/0, capture_month_name/0, щоб вони повертали рядок тексту зі шаблоном, який захоплює відповідні частини під назвами: "day", "month", "year", "day_name", "month_name"
DateParser.capture_month_name()
|> Regex.compile!()
|> Regex.named_captures("December")
# => %{"month_name" => "December"}
Реалізуйте capture_numeric_date/0, capture_month_name_date(), і capture_day_month_name_date/0, щоб вони повертали рядок тексту зі шаблоном, який захоплює частини з частини 3, використовуючи відповідний формат дати:
"01/01/1970"
"January 1, 1970"
"Thursday, January 1, 1970"
DateParser.capture_numeric_date()
|> Regex.compile!()
|> Regex.named_captures("01/01/1970")
# => %{"day" => "01", "month" => "01", "year" => "1970"}
Реалізуйте match_numeric_date/0, match_month_name_date/0 і match_day_month_name_date/0, щоб вони повертали скомпільований регулярний вираз, який зіставляється лише з датою і водночас може захоплювати частини.
"Thursday, January 1, 1970 was the Unix epoch." =~ DateParser.match_day_month_name_date()
# => false
"Thursday, January 1, 1970" =~ DateParser.match_day_month_name_date()
# => true
DateParser.match_day_month_name_date()
|> Regex.named_captures("Thursday, January 1, 1970")
# => %{
# "day" => "1",
# "day_name" => "Thursday",
# "month_name" => "January",
# "year" => "1970"
# }
Зареєструйтеся на Exercism, щоб вивчати й опановувати Elixir, а також 58 концепцій168 вправ та справжнє наставництво від людей, і все це безкоштовно.