# Домашнє завдання · Кортежі й розпакування

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

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

Дозволено з бібліотек: `math`, `collections`. Більше нічого не треба.
Циклів, умов і власних функцій тема ще не проходила — але якщо ти вже знаєш їх
з іншого джерела, користуйся спокійно: перевіряється результат, а не засоби.

Вихідні дані для всіх рівнів:

```python
міста = (
    ("Київ",   50.45, 30.52),
    ("Львів",  49.84, 24.03),
    ("Одеса",  46.48, 30.73),
    ("Харків", 49.99, 36.23),
)
```

---

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

### 1.1 · Кома, яка все ламає

Напиши чотири рядки, які створюють: число, кортеж з одного елемента,
той самий кортеж без дужок і порожній кортеж. Надрукуй для кожного значення
його `type(...).__name__` і `len(...)` там, де довжина існує.

**Зроблено, якщо:** у виводі видно, що `(7)` — це `int`, а `(7,)` і `7,` —
однакові кортежі довжиною 1, і виконуються перевірки

```python
assert type(без_коми) is int
assert з_комою == без_дужок and len(з_комою) == 1
assert len(порожній) == 0 and type(порожній) is tuple
```

### 1.2 · Розпакування запису міста

Візьми перший запис із `міста` і розпакуй його трьома різними шаблонами:

1. на три окремі імена `назва, широта, довгота`;
2. на назву й решту через зірочку: `назва, *координати`;
3. вкладено, попередньо перепакувавши запис у форму `("Київ", (50.45, 30.52))`.

Надрукуй результат кожного шаблону.

**Зроблено, якщо:** усі три шаблони дають ті самі числа, і це доведено:

```python
assert (широта, довгота) == tuple(координати) == пара_з_вкладеного
assert isinstance(координати, list), "зірочка дає СПИСОК, а не кортеж"
```

### 1.3 · Спіймай обидві помилки

Спробуй змінити елемент кортежа й спробуй дописати в нього елемент.
Обидві спроби мають впасти. Випиши **словами** (коментарем у коді),
чим `TypeError` у першому випадку відрізняється від `AttributeError` у другому.

**Зроблено, якщо:** у виводі є обидва повні тексти помилок і твоє пояснення
різниці між «операція заборонена для цього типу» і «такого методу не існує».

---

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

### 2.1 · Найпівнічніше місто без сортування на місці

Знайди місто з найбільшою широтою. Кортеж `міста` змінювати **не можна** —
і не вийде: у нього немає `sort()`. Скористайся `sorted(...)`, який повертає
новий список, або `max(...)` з `key`.

Надрукуй назву міста-переможця та його координати парою.

**Зроблено, якщо:** твій код друкує `Київ` із парою `(50.45, 30.52)`, а вихідний
кортеж лишився недоторканим:

```python
assert міста[0][0] == "Київ" and len(міста) == 4
assert найпівнічніше[0] == "Київ"
```

Додатково поясни двома реченнями, чому `sorted(міста)` спрацює, а `міста.sort()` — ні.

### 2.2 · Довжина замкненого маршруту

Порахуй довжину маршруту Київ → Львів → Одеса → Харків → Київ.
Відстань між двома точками бери як `math.dist(точка_1, точка_2)`,
а самі точки діставай із записів розпакуванням, а не індексами
(`_, широта, довгота = запис`).

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

```python
assert math.isclose(довжина, відрізок_1 + відрізок_2 + відрізок_3 + відрізок_4)
assert довжина >= 2 * max_пряма
```

### 2.3 · Пастка незмінності

Створи кортеж `спостереження = ("Київ", [12.0, 13.5, 11.2])`, де список —
це виміри температури. Зроби так, щоб:

- спроба замінити список цілком (`спостереження[1] = [...]`) впала;
- дописування нового виміру (`спостереження[1].append(...)`) спрацювало;
- `hash(спостереження)` теж впав.

**Зроблено, якщо:** у виводі видно всі три результати, і ти показав числом,
що адреса списку не змінилась:

```python
адреса_до = id(спостереження[1])
спостереження[1].append(14.1)
assert id(спостереження[1]) == адреса_до, "слот кортежа мав лишитись тим самим"
assert спостереження[1] == [12.0, 13.5, 11.2, 14.1]
```

І поясни двома реченнями, чому `hash` падає саме через це.

---

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

### 3.1 · Симетричний ключ-пара

Побудуй словник відстаней, у якого ключ — пара міст, а значення — відстань
між ними. Проблема: `("Київ", "Львів")` і `("Львів", "Київ")` — це **два різні
ключі**, бо кортежі порівнюються поелементно, зліва направо.

Зроби ключ таким, щоб порядок міст не мав значення, і заповни словник для всіх
шести пар із чотирьох міст.

**Зроблено, якщо:** у словнику рівно 6 записів, і виконується

```python
assert len(відстані) == 6
assert ключ("Київ", "Львів") == ключ("Львів", "Київ")
assert math.isclose(відстані[ключ("Львів", "Київ")], math.dist(київ, львів))
```

> Підказка вже була в практиці: `tuple(sorted(...))`. Подумай, чому саме
> `tuple(...)`, а не просто `sorted(...)`.

### 3.2 · Свій `namedtuple` на голому кортежі

Не використовуючи `collections.namedtuple`, зроби «іменований доступ» до записів:
словник `поля = {"назва": 0, "широта": 1, "довгота": 2}` і функцію
`поле(запис, імʼя)`, яка повертає потрібний елемент.

Потім зроби те саме через справжній `namedtuple` і порівняй.

**Зроблено, якщо:** обидва підходи дають однакові значення на всіх чотирьох
містах, і ти сформулював двома-трьома реченнями, що саме дає `namedtuple`
понад твій словник полів:

```python
Місто = namedtuple("Місто", "назва широта довгота")
київ_іменований = Місто(*міста[0])          # розпакування кортежа в аргументи

assert поле(міста[0], "широта") == київ_іменований.широта
assert isinstance(київ_іменований, tuple)
assert київ_іменований == міста[0]
```

### 3.3 · Де кортеж перестає бути надійним

Досліди межу гарантії незмінності. Побудуй два кортежі однакової форми:
один із самих лише незмінних елементів, другий — зі списком усередині.
Для кожного перевір три речі: чи береться `hash`, чи можна покласти в множину
`{...}` і чи змінюється вміст після дії над внутрішнім обʼєктом.

**Зроблено, якщо:** результати зведені в таблицю (можна просто трьома
`print`-ами по рядку), усі три перевірки для другого кортежа дають «ні / ні / так»,
і сформульоване правило одним реченням: за яких умов кортеж справді гарантує
незмінність даних, а не лише незмінність набору посилань.

---

## Підказки

- **Не бійся `assert`.** Це не «перевірка для викладача», а спосіб зафіксувати,
  що саме ти вважаєш правильним. Коли він падає — ти дізнався щось нове про свій
  код, і це найдешевший спосіб дізнатися.
- **У 2.1 стеж за тим, що повертає функція.** `sorted()` завжди повертає **список**,
  навіть якщо на вхід дали кортеж. Якщо тобі потрібен кортеж назад — приведи явно.
- **У 3.1 подумай про хешованість, а не про сортування.** Питання не в тому,
  як відсортувати два рядки, а в тому, чому результат сортування не можна
  покласти в ключ без перетворення.
- **Помилки читай до кінця.** `TypeError: 'tuple' object does not support item
  assignment` і `AttributeError: 'tuple' object has no attribute 'append'` —
  це дві різні історії, і плутати їх на співбесіді нічим не краще, ніж у коді.
