Треки
/
Elixir
Elixir
/
Вправи
/
Парсер дат
Парсер дат

Парсер дат

Навчальна вправа

Вступ

Регулярні вирази

Регулярні вирази (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"

Ми бачимо, що між ними є спільні риси, і вирішуємо написати кілька шаблонів регулярних виразів, які можна поєднувати.

1. Зіставити день, місяць і рік із дати

Реалізуйте 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

2. Зіставити день тижня і місяць року

Реалізуйте day_names/0 і month_names/0, щоб вони повертали рядок тексту зі шаблоном, який після компіляції зіставлявся б відповідно з назвою дня тижня та назвою місяця року.

"Tuesday" =~ DateParser.day_names() |> Regex.compile!()
# => true
"June" =~ DateParser.month_names() |> Regex.compile!()
# => true

3. Захопити день, місяць і рік

Реалізуйте 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"}

4. Поєднати захоплення, щоб захопити всю дату

Реалізуйте 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"}

5. Звузити захоплення, щоб зіставляти лише дату

Реалізуйте 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"
#   }
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
Elixir Exercism

Час розпочати Парсер дат?

Зареєструйтеся на Exercism, щоб вивчати й опановувати Elixir, а також 58 концепцій168 вправ та справжнє наставництво від людей, і все це безкоштовно.