Треки
/
jq
jq
/
Вправи
/
Регулярний чатбот
Регулярний чатбот

Регулярний чатбот

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

Вступ

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

Регулярні вирази (regex) - це послідовності символів, які задають шаблон пошуку в тексті.

Вивчення синтаксису регулярних виразів виходить за межі цієї теми. Ми зосередимося на виразах, які jq надає для роботи з регулярними виразами.

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

Різні інструменти реалізують різні версії регулярних виразів. jq використовує бібліотеку регулярних виразів Oniguruma, яка здебільшого сумісна з регулярними виразами Perl v5.8.

Конкретний синтаксис, який використовує jq, можна знайти в репозиторії Oniguruma на GitHub.

Caution

jq не має спеціального синтаксису для регулярних виразів. Вони просто подаються як рядки тексту (англ. string). Це означає, що всі зворотні скісні риски в регулярному виразі потрібно екранувати в рядку тексту.

Наприклад, клас символів-цифр (\d) потрібно записати як "\\d".

Функції для регулярних виразів

Регулярні вирази в jq обмежені набором фільтрів.

Просте зіставлення

Коли потрібно зʼясувати, чи рядок тексту відповідає шаблону, використовуємо фільтр test.

STRING | test(REGEX)
STRING | test(REGEX; FLAGS)
STRING | test([REGEX, FLAGS])

Цей фільтр видає булеве значення (англ. Boolean) як результат.

"Hello World!" | test("W")    # => true
"Goodbye Mars" | test("W")    # => false
Інформація про збіг

Коли потрібно витягти підрядок, який насправді збігся з шаблоном, використовуємо фільтр match.

STRING | match(REGEX)
STRING | match(REGEX; FLAGS)
STRING | match([REGEX, FLAGS])

Цей фільтр видає:

  • нічого, якщо збігу не було, або
  • обʼєкт із різними властивостями, якщо збіг був.

У цьому прикладі ми шукаємо два однакові голосні звуки підряд, використовуючи синтаксис зворотного посилання, \1.

"Hello World!" | match("([aeiou])\\1")
# => empty

"Goodbye Mars" | match("([aeiou])\\1")
# => {
#      "offset": 1,
#      "length": 2,
#      "string": "oo",
#      "captures": [
#        {
#          "offset": 1,
#          "length": 1,
#          "string": "o",
#          "name": null
#        }
#      ]
#    }

Фільтр match повертає обʼєкт для кожного збігу. У цьому прикладі показано, як працює прапорець "g" для пошуку всіх голосних.

"Goodbye Mars" | match("[aeiou]"; "g")
# => { "offset": 1, "length": 1, "string": "o", "captures": [] }
#    { "offset": 2, "length": 1, "string": "o", "captures": [] }
#    { "offset": 6, "length": 1, "string": "e", "captures": [] }
#    { "offset": 9, "length": 1, "string": "a", "captures": [] }
Захоплені підрядки

Подібно до фільтра match, фільтр capture повертає обʼєкт, якщо збіг був.

STRING | capture(REGEX)
STRING | capture(REGEX; FLAGS)
STRING | capture([REGEX, FLAGS])

Повернений обʼєкт - це відображення іменованих захоплень.

"JIRAISSUE-1234" | capture("(?<project>\\w+)-(?<issue_num>\\d+)")
# => {
#      "project": "JIRAISSUE",
#      "issue_num": "1234"
#    }
Лише підрядки

Фільтр scan подібний до match із прапорцем "g".

STRING | scan(REGEX)
STRING | scan(REGEX; FLAGS)

scan видає потік підрядків.

"Goodbye Mars" | scan("[aeiou]")
# => "o"
#    "o"
#    "e"
#    "a"

Використовуємо конструктор масиву [...], щоб зібрати підрядки.

"Goodbye Mars" | [ scan("[aeiou]") ]
# => ["o", "o", "e", "a"]
Розбиття рядка тексту

Якщо відомо, які частини рядка тексту потрібно залишити, використовуємо match або scan. Якщо відомо, які частини потрібно відкинути, використовуємо split.

STRING | split(REGEX; FLAGS)
Caution

Фільтр split з арністю 1 сприймає свій аргумент як фіксований рядок тексту.

Щоб використати регулярний вираз із split, потрібно надати другий аргумент; порожній рядок тексту цілком підходить.

Приклад, який розбиває рядок тексту за довільними пробільними символами.

"first   second           third fourth" | split("\\s+"; "")
# => ["first", "second", "third", "fourth"]
Заміни

Фільтри sub і gsub можуть перетворювати вхідний рядок тексту, замінюючи ті частини вхідних даних, що збіглися, на рядок тексту-заміну.

Щоб замінити лише перший збіг, використовуємо sub. Щоб замінити всі збіги, використовуємо gsub.

STRING | sub(REGEX; REPLACEMENT)
STRING | sub(REGEX; REPLACEMENT; FLAGS)
STRING | gsub(REGEX; REPLACEMENT)
STRING | gsub(REGEX; REPLACEMENT; FLAGS)
"Goodnight kittens. Goodnight mittens." | sub("night"; " morning")
# => "Good morning kittens. Goodnight mittens."

"Goodnight kittens. Goodnight mittens." | gsub("night"; " morning")
# => "Good morning kittens. Good morning mittens."

Текст заміни може посилатися на збіжні підрядки; використовуємо іменовані захоплення та інтерполяцію рядків.

"Some 3-letter acronyms: gnu, csv, png"
| gsub( "\\b(?<tla>[[:alpha:]]{3})\\b";     # find words 3 letters long
        "\(.tla | ascii_upcase)" )          # upper-case the match
# => "Some 3-letter acronyms: GNU, CSV, PNG"

Прапорці

У всіх наведених вище фільтрах FLAGS - це рядок тексту, що складається з нуля або більше підтримуваних прапорців.

  • g - Глобальний пошук (знайти всі збіги, а не лише перший)
  • i - Пошук без урахування регістру
  • m - Багаторядковий режим ('.' збігатиметься з символами нового рядка)
  • n - Ігнорувати порожні збіги
  • p - Увімкнено обидва режими, s і m
  • s - Однорядковий режим ('^' -> '\A', '$' -> '\Z')
  • l - Знайти найдовші можливі збіги
  • x - Розширений формат регулярного виразу (ігнорувати пробільні символи та коментарі)

Наприклад

"JIRAISSUE-1234" | capture("(?<project>\\w+)-(?<issue_num>\\d+)")

# or with Extended formatting

"JIRAISSUE-1234" | capture("
                     (?<project>   \\w+ )  # the Jira project
                     -                     # followed by a hyphen
                     (?<issue_num> \\d+ )  # followed by digits
                   "; "x")

Вказівки

Нас найняли на посаду спеціаліста з регулярних виразів у компанію, яка розробляє Чатбота.

Він перебуває на дуже ранній стадії розробки. Наша місія - навчити Чатбота за допомогою регулярних виразів краще розуміти й генерувати природну мову.

1. Перевірка коректної команди

Окрім того, що Чатбот розумний, він ще й відданий помічник. Щоб щось запитати в Чатбота, користувач має сказати слово «Chatbot» на першій позиції команди. Неважливо, чи записане ключове слово великими літерами, чи малими. Важлива саме позиція цього слова.

Реалізуйте функцію is_valid_command, яка допомагає Чатботу розпізнавати, коли користувач дає команду.

"Chatbot, play a song from the 80's." | is_valid_command
# => true
"Hey Chatbot, where is the closest pharmacy?" | is_valid_command
# => false
"CHATBOT, do you have a solution for this challenge?" | is_valid_command
# => true

2. Видалення зашифрованих емодзі

Чатботу складно зрозуміти, як люди використовують емодзі, щоб виражати свої емоції. Коли Чатбот отримує повідомлення від користувача, кожне емодзі подане як рядок тексту (англ. string) "emoji", за яким іде номер id.

Реалізуйте метод remove_emoji, який приймає рядок тексту й видаляє всі емодзі з повідомлення.

Рядки, у яких немає емодзі, повертайте без змін. Просто видаліть рядок "emoji". Не змінюйте пробільні символи.

"I love playing videogames emoji3465 it's one of my hobbies" | remove_emoji
# => "I love playing videogames  it's one of my hobbies"

3. Перевірка коректного номера телефону

У якийсь момент спілкування з Чатботом користувач надасть номер телефону. Чатбот може зателефонувати лише на номер певного формату.

Реалізуйте функцію check_phone_number.

Якщо номер коректний, Чатбот відповідає повідомленням, у якому дякує користувачеві й підтверджує номер. Якщо номер некоректний, Чатбот повідомляє користувачеві, що номер телефону неправильний.

Очікуваний формат - (+NN) NNN-NNN-NNN, де N позначає цифру.

"chatbot my phone number is (+34) 659-771-594" | check_phone_number
# => "Thanks! Your phone number is OK."
"chatbot, call me at 659-771-594" | check_phone_number
# => "Oops, it seems like I can't reach out to 659-771-594."

4. Отримання посилання на вебсайт

Чатбот вирізняється надзвичайною допитливістю. Хоча він уміє шукати в інтернеті інформацію на певну тему, він любить розпитувати користувачів про цікаві вебсайти, де можна знайти щось доречне.

Приклад розмови:

Чатбот: Вітаю! Я хотів би навчитися програмувати на JavaScript. Чи є цікавий вебсайт, де я міг би цього навчитися?

Користувач: Я багато чого навчився на exercism.org, там багато чудового матеріалу.

Реалізуйте функцію get_domains, яка повертає масив доменів вебсайтів.

"I learned a lot from exercism.org and google.com" | get_domains
# => ["exercism.org", "google.com"]

5. Привітання користувача

Ввічливий Чатбот звертається до користувачів на імʼя. Коли користувач представляється, наш Чатбот розпізнає його імʼя й відповідає привітанням.

Напишіть функцію nice_to_meet_you.

Якщо вхідний рядок тексту містить "My name is Someone.", захопіть імʼя та поверніть рядок "Nice to meet you, Someone.".

"My name is Jean-Luc" | nice_to_meet_you
# => "Nice to meet you, Jean-Luc"

6. Дуже простий розбір CSV

Піддавшись «повзучому розростанню функцій», ми додамо до Чатбота функцію розбору CSV.

Реалізуйте функцію parse_csv, яка приймає рядок тексту й повертає масив отриманих полів. Роздільником полів має бути «кома плюс необовʼязкові пробільні символи».

Не будемо перейматися крайніми випадками формату CSV (наприклад, полями, що містять коми).

"first, second,third,   fourth" | parse_csv
# => ["first", "second", "third", "fourth"]
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
jq Exercism

Час розпочати Регулярний чатбот?

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