# Домашнє завдання · Регулярні вирази

> Лекція: [lecture.html](lecture.html) · Практика: [practice.ipynb](practice.ipynb) ·
> Тест: [quiz.html](quiz.html)

Працюємо з тим самим журналом, що й у лекції. Він навмисно «брудний»: рядки різного
формату, зайві пробіли, різний регістр рівнів. Скопіюй його до себе:

```python
ЖУРНАЛ = """2026-03-15 09:41 INFO  #1043 Олена kava@lviv.ua +380671234567 249.50 грн
2026-03-15 09:42 warn  #1044 Петро petro@ua.net +380509876543 1290.00 грн
2026-03-16 11:03 ERROR #1045 Ірина iryna@mail.com +38 (063) 111-22-33 75.00 грн
2026-03-16 11:07 INFO  #1046 Богдан-Тарас bt.koval+shop@ua-mail.net 0951234567 3.99 грн
непридатний рядок без жодних даних
2026-03-17 08:15 error #1047 Ґалина galyna@ukr.net +380671110099 15200.00 грн"""
```

---

## 🟢 Рівень 1 — База

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

Окремо зроби четверте завдання **без регулярки** — порахуй, скільки рядків
починаються з `2026-03-16`. Використай `.startswith()` і поясни в коментарі,
чому тут шаблон був би зайвий.

**Зроблено, якщо:**

- `номери == ['1043', '1044', '1045', '1046', '1047']` (без решітки — саме цифри);
- у списку пошти рівно пʼять адрес, серед них `bt.koval+shop@ua-mail.net`
  (тобто твій шаблон витримує крапку, плюс і дефіс — і в імені, і в домені);
- сума всіх замовлень дорівнює `16818.49`;
- підрахунок рядків за 16 березня зроблено методом рядка, і в коді є коментар,
  який пояснює чому.

---

## 🟡 Рівень 2 — Плюс

Напиши функцію `розібрати_рядок(рядок)`, яка повертає **словник** із полями
`дата`, `час`, `рівень`, `номер`, `клієнт`, `пошта`, `телефон`, `сума` —
або `None`, якщо рядок не підходить під формат.

Вимоги до шаблону:

1. усі групи **іменовані**, шаблон записаний із `re.VERBOSE` і коментарями;
2. рівень розпізнається незалежно від регістру (`INFO`, `info`, `Info`), але
   в результаті завжди зберігається великими літерами;
3. імʼя клієнта може містити дефіс (`Богдан-Тарас`) і українські літери,
   зокрема `ґ` і `Ґ`;
4. телефон приймається в усіх трьох формах, що є в журналі — `+380671234567`,
   `+38 (063) 111-22-33` і `0951234567` — і в результаті завжди зберігається
   як `+380XXXXXXXXX`;
5. `сума` в словнику має бути числом `float`, а не рядком.

Далі побудуй словник `сума_за_рівнем` і виведи його впорядкованим за спаданням суми.

**Зроблено, якщо:**

- `розібрати_рядок("непридатний рядок без жодних даних") is None`;
- усі пʼять придатних рядків розібрано, `len(записи) == 5`;
- `записи[1]["рівень"] == "WARN"` і `записи[2]["телефон"] == "+380631112233"`;
- `сума_за_рівнем == {"ERROR": 15275.0, "WARN": 1290.0, "INFO": 253.49}`;
- у коді немає жодного `.group(1)` — тільки імена.

---

## 🔴 Рівень 3 — Виклик

Дослідження на дві частини.

**Частина А · шаблон проти методів.** Візьми задачу «розбити рядок журналу на поля».
Реалізуй її двома способами: через свій шаблон із рівня 2 і через звичайні
`.split()`, `.strip()`, зрізи. Виміряй обидва на журналі, повтореному 20 000 разів
(`ЖУРНАЛ * 20000`), і зроби чесний висновок: де швидше, де читабельніше і за яких
умов ти обрав би кожен варіант. Окремо виміряй скомпільований шаблон
(`re.compile`) проти `re.search` у циклі.

**Частина Б · катастрофічний відкат.** Для `n` від 12 до 23 виміряй час
`re.search(r"(a+)+b", "a" * n)`. Побудуй таблицю «n → час → відношення до
попереднього». Потім:

- поясни, чому відношення тримається біля двійки;
- знайди шаблон, який розпізнає ту саму мову («одна або більше «a», потім «b»»),
  але працює лінійно, і доведи це вимірюванням;
- перевір, чи зникає проблема, якщо в тексті **є** літера `b` у кінці, і поясни
  результат.

**Зроблено, якщо:**

- є таблиця вимірів для обох частин, а не одне число;
- для `(a+)+b` відношення сусідніх часів на великих `n` лежить у межах 1.7–2.5,
  і ти пояснив словами, звідки береться двійка;
- для безпечного шаблону час на 23 символах щонайменше в тисячу разів менший
  за час небезпечного на тих самих 23 символах;
- у висновку сказано, за якої умови вкладений квантифікатор небезпечний
  (а не просто «вкладені квантифікатори погані»).

---

## Підказки

- Українські літери не вміщаються в діапазон `[а-я]`. Пиши `[а-щьюяіїєґ]` для
  малих і `[А-ЩЬЮЯІЇЄҐ]` для великих — або просто `\w`, якщо цифри й підкреслення
  тобі не завадять.
- Щоб звести телефон до єдиного вигляду, спершу прибери все зайве
  (`re.sub(r"\D", "", телефон)`), а вже потім перевіряй довжину й початок.
  Не намагайся описати всі формати одним шаблоном — це шлях до нечитабельності.
- `re.VERBOSE` ігнорує пробіли всередині шаблону. Якщо пробіл потрібен саме
  як символ — пиши `\s` або `[ ]`, інакше він мовчки зникне.
- Вимірюючи час, повторюй вимір тричі й бери мінімум: перший запуск завжди
  дорожчий, бо шаблон ще не в кеші.
- Якщо результат `findall` раптом складається з кортежів — у шаблоні є групи.
  Або зроби їх незапамʼятовними `(?:...)`, або переходь на `finditer`.
