# Домашнє завдання · Рядки

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

Три рівні. Кожен наступний спирається на попередній, але братися можна за будь-який.
Пиши в окремому файлі `homework_05.py` або в новому зошиті — головне, щоб код
запускався й друкував результат.

З бібліотек не потрібно нічого: усе робиться вбудованими можливостями рядка.
Єдиний виняток — `unicodedata` у завданні 3.2, і то лише для перевірки.

---

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

### 1.1 · Розбір «брудного» запису

Дано рядок, у якому поля розділені крапкою з комою, а навколо кожного — випадкові
пробіли:

```python
сирий = "   Львів ;  чай зелений ; 1250.00 ;  2   "
```

Розбери його на чотири значення: `місто` (рядок), `товар` (рядок), `ціна` (`float`)
і `кількість` (`int`). Кожне поле мусить бути очищене від пробілів з обох боків.
Надрукуй усі чотири по одному в рядок разом із типом.

**Зроблено, якщо:** виконуються всі чотири перевірки
```python
assert місто == "Львів"
assert товар == "чай зелений"          # пробіл ВСЕРЕДИНІ має лишитися
assert ціна == 1250.0 and isinstance(ціна, float)
assert кількість == 2 and isinstance(кількість, int)
```

> Пильнуй за третім рядком: `.strip()` знімає пробіли лише з країв. Якщо в тебе
> вийшло `"чайзелений"` — ти десь застосував `.replace(" ", "")`, а це вже інша
> операція.

### 1.2 · Символи проти байтів

Для тих самих чотирьох полів надрукуй таблицю: поле, `len()` у символах,
`len(поле.encode())` у байтах і різницю між ними.

**Зроблено, якщо:** твій код друкує таблицю, з якої видно, що різниця дорівнює
кількості кириличних літер у полі, і виконується
```python
assert len("Львів") == 5
assert len("Львів".encode()) == 10
assert len("1250.00") == len("1250.00".encode())   # чиста ASCII — байт у байт
```

---

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

### 2.1 · Рядок чека фіксованої ширини

Склади однорядковий чек рівно **46 символів** завширшки:

- назва товару притиснута ліворуч;
- сума (`ціна * кількість`) притиснута праворуч, з двома знаками після коми
  й роздільником тисяч;
- проміжок між ними заповнений крапками.

Для `("чай зелений", 2, 1250.00)` має вийти:

```
чай зелений...........................2,500.00
```

Зроби це **однією** f-специфікацією для проміжку — тобто спершу порахуй, скільки
символів лишилось під крапки, а потім скористайся заповнювачем у міні-мові формату.

**Зроблено, якщо:**
```python
assert len(чек) == 46
assert чек.startswith("чай зелений")
assert чек.endswith("2,500.00")
assert чек.count(".") == 27 + 1        # 27 крапок-заповнювачів плюс десяткова
```

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

### 2.2 · Скільки коштує неохайність

Візьми той самий `сирий` рядок із завдання 1.1 і **не** роби `.strip()`.
Розбий його по `";"` і надрукуй порівняння: для кожного поля покажи
`repr(поле)`, `поле == очікуване` і `поле.strip() == очікуване`.

**Зроблено, якщо:** у виводі чітко видно, що без `.strip()` жодне з чотирьох
порівнянь не спрацювало, і виконується
```python
assert поля_без_strip[0] != "Львів"
assert поля_без_strip[0].strip() == "Львів"
```
І окремо: поясни двома реченнями, чому помилка такого роду найдорожча —
дані виглядають правильними на екрані, а порівняння дає `False`.

---

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

### 3.1 · Розвертання слів без циклів і без `reversed`

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

```python
розвернути_слова("кава мелена холодна")   # -> "холодна мелена кава"
```

Дозволено лише: `.split()`, зрізи (у тому числі `[::-1]`), `.join()`.
Заборонено: `for`, `while`, `reversed`, `list.reverse`, `sorted`.

**Зроблено, якщо:** виконуються всі чотири перевірки
```python
assert розвернути_слова("кава мелена холодна") == "холодна мелена кава"
assert розвернути_слова("одне") == "одне"
assert розвернути_слова("  зайві   пробіли  ") == "пробіли зайві"
assert розвернути_слова("") == ""
```

> Третя перевірка не випадкова: вона проходить лише тоді, коли ти взяв
> `.split()` **без аргументу**. З аргументом `" "` там з'являться порожні рядки.

### 3.2 · Свій `casefold` для української

Порівняння рядків без урахування регістру виглядає просто, поки не з'явиться
`ß`. Досліди межу застосовності на конкретних прикладах.

Напиши функцію `однакові_без_регістру(a, b)`, яка повертає `True`, якщо рядки
збігаються з точністю до регістру. Перевір її на п'яти парах і склади таблицю:
пара, результат через `.lower()`, результат через `.casefold()`, і чи вони
розійшлись.

Обов'язкові пари:
```python
("Київ", "КИЇВ")             # тут усе нудно й правильно
("Straße", "STRASSE")        # німецька ß
("ﬁle", "file")              # типографська лігатура fi — один символ!
("Σ", "ς")                   # грецька сигма: велика одна, малих дві
("İstanbul", "istanbul")     # турецька І з крапкою
```

**Зроблено, якщо:** у таблиці видно рівно **три** пари, де `.lower()`
і `.casefold()` дають різні відповіді, і ти назвав ту єдину пару, де
обидва способи однаково помиляються. Поясни двома-трьома реченнями,
чому «зробити маленькими й порівняти» — це не універсальне правило.

Для перевірки, що саме сталося з символом, стане в пригоді:
```python
import unicodedata
print(unicodedata.name("ß"), "->", [unicodedata.name(c) for c in "ß".upper()])
```

### 3.3 · Скільки насправді коштує `+=`

Не міряй час — він залежить від машини. Порахуй **роботу**: напиши функцію
`скопійовано_символів(n, L)`, яка повертає, скільки символів скопіює
`результат += шматок` на `n` кроках при довжині шматка `L`.

Зроби це двома способами: покроковим підрахунком і замкненою формулою.

**Зроблено, якщо:** обидва способи збігаються на всіх значеннях, і виконуються
```python
assert скопійовано_формулою(1, 6) == 6
assert скопійовано_формулою(60, 6) == 10980
assert скопійовано_формулою(1000, 6) == 3_003_000
assert скопійовано_кроками(60, 6) == скопійовано_формулою(60, 6)
```
Додатково: порахуй, у скільки разів це більше за `join` при `n = 10_000`,
і напиши одним реченням, що саме росте — кількість об'єктів чи обсяг копіювання.

---

## Підказки

- **Заповнювач у міні-мові формату йде першим.** Специфікація
  `.<20` означає «ширина 20, притиснути ліворуч, доповнити крапками».
  Ширину можна підставити виразом: `f"{назва:.<{скільки}}"` — фігурні дужки
  всередині специфікації теж працюють.
- **У 2.1 рахуй з кінця.** Спершу відформатуй суму (вона має фіксований вигляд),
  подивись її довжину, а вже потім вирахуй, скільки лишилось під назву й крапки.
- **У 3.1 порядок дій має значення.** Розверни список слів, а не рядок:
  `текст.split()` дає список, а до списку теж можна застосувати `[::-1]` —
  зрізи працюють не лише з рядками (це буде тема 06).
- **У 3.2 не вгадуй, а друкуй.** Для кожного символу подивись, у що він
  перетворюється: `"ß".lower()`, `"ß".upper()`, `"ß".casefold()` — три різні
  відповіді, і саме на цьому все й ламається.
- **У 3.3 не будуй справжніх рядків.** Тобі потрібне лише число скопійованих
  символів; довжина проміжного результату після `k`-го кроку — це `k * L`,
  і рівно стільки символів копіюється на наступному кроці.
