Українська
Приклади програм і типові помилки
Приклади програм
Приклад 1. Частоти слів у навчальному тексті
Написати програму, яка об’єднує еквівалентні записи Unicode, ігнорує регістр, підраховує слова й знаходить найдовше слово. Домовимося, що в цьому прикладі слова розділені пробілами, а на краях слова може бути лише перелічена пунктуація.
py
import unicodedata
from collections import Counter
def words_of(text: str) -> list[str]:
normal = unicodedata.normalize("NFC", text.casefold())
words = []
for item in normal.split():
word = item.strip(".,!?;:«»")
if word:
words.append(word)
return words
def main() -> None:
text = "Кіт, кіт і їжак. Їжак!"
words = words_of(text)
for word, count in sorted(Counter(words).items()):
print(f"{word}: {count}")
longest = max(words, key=len, default="")
print(f"Найдовше: {longest or 'немає слів'}")
if __name__ == "__main__":
main()Результат:
кіт: 2
і: 1
їжак: 2
Найдовше: їжакПорожній список обробляється через default функції max. Рівність довжин розв’язується першим входженням. Сортування тут відбувається за кодовими точками; це не повна словникова абетка. Для тексту без пробілу після коми цей алгоритм потребував би іншого контракту токенізації. Явне обмеження краще, ніж непомітна зміна поняття «слово» під час підрахунку.
Приклад 2. Таблиця продажів
Скласти звіт за списком товарів: назва, кількість, ціна, сума та частка у виручці. Грошові значення зберігатимемо цілими копійками; перетворення на дробове число потрібне лише для відображення.
py
def main() -> None:
rows = [("Зошит", 3, 2500), ("Олівець", 2, 1250)]
total = sum(count * price for _, count, price in rows)
print(f"{'Товар':<10} {'К-ть':>4} {'Сума':>8} {'Частка':>7}")
for name, count, price in rows:
subtotal = count * price
share = subtotal / total if total else 0
print(f"{name:<10} {count:>4} "
f"{subtotal / 100:>8.2f} {share:>7.1%}")
print(f"Разом: {total / 100:.2f} грн")
if __name__ == "__main__":
main()Результат:
Товар К-ть Сума Частка
Зошит 3 75.00 75.0%
Олівець 2 25.00 25.0%
Разом: 100.00 грнЗверніть увагу на захист від ділення на нуль для порожнього або нульового звіту. Перевірка введення не потрібна для заданих у програмі правильних даних; у варіантах із введенням потрібно відхилити від’ємні кількості та ціни ще до обчислення підсумків.
Приклад 3. HTML-лист із текстовою підстановкою
Потрібно вітати користувача в абзаці, не сприймаючи введене ім’я як HTML. Дозволимо підстановки лише в тексті абзацу; у цьому обробнику перетворення й специфікації формату не підтримуються.
py
from html import escape
from string.templatelib import Template
def html_text(template: Template) -> str:
pieces = [template.strings[0]]
for index, field in enumerate(template.interpolations):
if field.conversion or field.format_spec:
raise ValueError("Перетворення не підтримується")
pieces.append(escape(str(field.value), quote=True))
pieces.append(template.strings[index + 1])
return "".join(pieces)
def main() -> None:
name = "<b>Оля & Іван</b>"
print(html_text(t"<p>Вітаємо, {name}!</p>"))
if __name__ == "__main__":
main()Результат:
<p>Вітаємо, <b>Оля & Іван</b>!</p>Сталі частини не екрануються, бо вони є довіреною розміткою. Значення обробляються перед з’єднанням, тому введені кутові дужки залишаться видимим текстом. Функцію не слід використовувати для підстановки імен тегів або вмісту script; її контракт навмисно вузький. Тест має містити амперсанд, лапки, порожній рядок і кілька сусідніх підстановок, щоб перевірити збереження меж.
Приклад 4. Розбір навчального журналу
Нехай запис має формат час метод шлях код: час – HH:MM:SS, метод – GET або POST, шлях не містить пробілів, код – число від 100 до 599. Перевіримо кожний рядок цілком і підрахуємо коди.
py
import re
from collections import Counter
PATTERN = re.compile(r"""
(?P<time>(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9])
[ ](?P<method>GET|POST)
[ ](?P<path>/\S*)
[ ](?P<status>[1-5][0-9]{2})
""", re.VERBOSE)
def main() -> None:
text = "12:30:00 GET /home 200\n12:31:00 POST /login 403"
counts: Counter[str] = Counter()
for number, line in enumerate(text.splitlines(), start=1):
match = PATTERN.fullmatch(line)
if match is None:
print(f"Рядок {number}: неправильний формат")
continue
counts[match.group("status")] += 1
for status, count in sorted(counts.items()):
print(f"{status}: {count}")
if __name__ == "__main__":
main()Результат: 200: 1, потім 403: 1. Вираз не пропускає 25:00:00 або рядок із зайвим полем. Якщо потрібен перелік позицій збігів у довільному тексті, застосовують finditer; для перевірки кожного запису правильним вибором тут є fullmatch.
Обмеження, перевірки та типові помилки
Не кожний текст потребує regex. Пошук буквального підрядка через in простіший, ніж search; розбиття за одним розділювачем через split ясніше за еквівалентний вираз. Для довільно вкладених структур і формальних мов потрібен аналізатор відповідного формату.
У виразах із неоднозначними вкладеними повтореннями, наприклад (a+)+, невдалий збіг на довгому вході може спричинити дуже багато повернень. Не демонструйте це на необмежено великих даних. У модулі re немає аргументу тайм-ауту для звичайного search: обмежуйте довжину вхідних рядків і спрощуйте шаблон. Лінивий квантифікатор сам собою не гарантує лінійного часу.
Під час перевірки валідатора використовуйте правильний рядок, порожній рядок, пропущений символ, зайвий символ, межові довжини, кінцевий перенос та схожі символи інших абеток. Для перетворювача додайте перевірку, що незмінені фрагменти збережені. Обмеження довжини перевіряйте до виконання складного виразу.
Таблиця 6.1. Типові помилки під час опрацювання рядків
| Помилка | Причина та виправлення |
|---|---|
text.strip() нічого не змінив | Рядок незмінний; збережіть повернене значення. |
strip(".txt") псує назву | Це набір символів; застосуйте removesuffix. |
len(data) дає іншу довжину | Байти й кодові точки мають різні одиниці. |
match пропускає зайвий хвіст | Для всього введення потрібен fullmatch. |
\d приймає незвичні цифри | Unicode-клас; за умовою використайте [0-9]. |
| t-рядок надрукував структуру | Потрібен обробник із визначеним контрактом. |
| Ширина таблиці порушена | Ширина мінімальна; обмежте довжину назв. |
Команди також можна розбирати без regex: виконайте split, а потім match зі списковими зразками на кшталт case ["find", word]:. Тут match – оператор Python із теми 2, а не функція re.match. Він працює з уже отриманими значеннями, тоді як регулярний вираз описує символи всередині рядка.