# tuple и set: фиксированная запись и коллекция без повторов<no value>

## Что мы изучим?

Ещё две структуры данных, у каждой своя «суперсила»:

- **tuple** (кортеж) — как список, но **фиксированный**: после создания его нельзя изменить.
- **set** (множество) — коллекция **без повторов** и без порядка, которая позволяет очень быстро проверить, есть ли в ней значение.

## Зачем это нужно?

- **tuple** подходит для данных, которые представляют **одну запись с фиксированной структурой**: точка `(x, y)`, дата `(2026, 10, 6)`, строка из базы данных `(7, "Dana", 24)`. В темах бэкенда и баз данных **каждая строка, которая возвращается из базы данных, будет приходить как tuple!** Неизменяемость гарантирует, что никакой код не изменит запись «по ошибке».
- **set** подходит для случаев, когда нужна **уникальность**: сколько **разных** пользователей зашло сегодня на сайт? Какие теги у поста? Кто записан сразу на два курса?

## Объяснение с основ

### tuple

**tuple** — **упорядоченная** последовательность значений, **которую нельзя изменить** (Immutable). После создания нельзя добавлять, удалять или заменять элементы.

- Записывается в **круглых** скобках: `(1, 2, 3)`.
- Доступ к элементам как у списка: `point[0]`, `point[-1]`, `len(point)`, `in`.
- Нет `append`, нет `remove` и нельзя присвоить значение элементу.

**Распаковка** (Unpacking): tuple можно «распаковать» прямо в переменные:

```python
student = ("Dana", 24, "Haifa")
name, age, city = student
```

Мы уже пользовались этим незаметно: `for name, age in attempts:` и `for position, name in enumerate(...)`.

{{< box "analogy" "Аналогия" >}}
**list** — как список покупок на доске: добавляешь, стираешь, меняешь. **tuple** — как дата рождения в удостоверении личности: напечатана один раз, и никто не должен её менять.
{{< /box >}}

### set

**set** — коллекция **уникальных** значений:

- **Без повторов** — добавление уже существующего значения ничего не делает.
- **Без порядка** — индексов нет. `my_set[0]` — ошибка. Порядок при печати может быть любым.
- **Изменяемый** — можно добавлять и удалять.
- **Очень быстрая проверка `in`**, даже при миллионе элементов. В списке `in` идёт элемент за элементом.
- Записывается в **фигурных** скобках: `{1, 2, 3}`. **Но** пустое множество создают через `set()`, потому что `{}` — это пустой dict (следующий урок).

### Операции над множествами

| Операция | Оператор | Значение |
|---|---|---|
| Объединение (union) | `a \| b` | Всё, что есть в a **или** в b |
| Пересечение (intersection) | `a & b` | То, что есть **и** в a, **и** в b |
| Разность (difference) | `a - b` | То, что есть в a **и нет** в b |

## Синтаксис

```python
# tuple
point = (3, 5)
single = (7,)              # a tuple with ONE item needs a comma!
x, y = point               # unpacking
point[0]                   # 3

# set
tags = {"python", "web"}
empty_set = set()          # NOT {} - that is an empty dict
tags.add("api")            # add one item
tags.remove("web")         # remove (KeyError if missing)
tags.discard("xyz")        # remove if present, no error if missing
"python" in tags           # fast membership test
unique = set([1, 1, 2])    # {1, 2} - remove duplicates from a list

a | b    # union
a & b    # intersection
a - b    # difference
```

## Первый пример кода

`week-2/tuples_demo.py`:

```python
student = ("Dana", 24, "Haifa")

print("Full record:", student)
print("Name:", student[0])
print("Fields:", len(student))

name, age, city = student
print(name, "is", age, "and lives in", city)

student[1] = 25
```

**Вывод:**

```text
Full record: ('Dana', 24, 'Haifa')
Name: Dana
Fields: 3
Dana is 24 and lives in Haifa
Traceback (most recent call last):
  File "tuples_demo.py", line 10, in <module>
    student[1] = 25
TypeError: 'tuple' object does not support item assignment
```

## Разбор кода построчно

| Строка | Объяснение |
|---|---|
| `student = ("Dana", 24, "Haifa")` | tuple с 3 полями. **Позиция задаёт смысл**: 0 = имя, 1 = возраст, 2 = город. |
| `student[0]` | Доступ как в списке. |
| `name, age, city = student` | **Распаковка**: первый элемент идёт в `name`, второй — в `age`, третий — в `city`. Число переменных должно совпадать с числом элементов. |
| `student[1] = 25` | Попытка изменить, и выбрасывается `TypeError`. **Это не сбой, это защита**, которую даёт tuple. |

Хотите «изменить» tuple? Создайте **новый**: `student = (student[0], 25, student[2])`.

## Ещё один пример

**Анализ посетителей сайта с помощью set.** `week-2/visitors.py`:

```python
monday_visits = ["dana", "avi", "dana", "noa", "avi", "dana"]
tuesday_visits = ["noa", "yossi", "rina", "noa"]

monday = set(monday_visits)
tuesday = set(tuesday_visits)

print("Monday page views:", len(monday_visits))
print("Monday unique visitors:", len(monday), monday)

print("Visited on both days:", monday & tuesday)
print("Visited on any day:", monday | tuesday)
print("Only on Monday:", monday - tuesday)

tuesday.add("dana")
tuesday.add("noa")
print("Tuesday after adds:", len(tuesday), "unique")
print("Did yossi visit on Monday?", "yossi" in monday)
```

**Вывод (порядок элементов внутри `{}` у вас может отличаться):**

```text
Monday page views: 6
Monday unique visitors: 3 {'avi', 'noa', 'dana'}
Visited on both days: {'noa'}
Visited on any day: {'dana', 'yossi', 'rina', 'avi', 'noa'}
Only on Monday: {'avi', 'dana'}
Tuesday after adds: 4 unique
Did yossi visit on Monday? False
```

**Разбор:**

- `set(monday_visits)` — превращает список в множество, и повторы исчезают: 6 просмотров, 3 посетителя.
- `monday & tuesday` — **пересечение**: только тот, кто есть в обоих (`noa`).
- `monday | tuesday` — **объединение**: все, кто заходил, каждый по одному разу.
- `monday - tuesday` — **разность**: заходили в понедельник и не заходили во вторник.
- `tuesday.add("noa")` — `noa` уже есть, поэтому ничего не происходит. Добавилась только `dana`, и их стало 4 (`noa`, `yossi`, `rina`, `dana`).

### Что когда использовать? Сравнительная таблица

| | list | tuple | set |
|---|---|---|---|
| Запись | `[1, 2]` | `(1, 2)` | `{1, 2}` |
| Порядок / индекс | ✓ | ✓ | ✗ |
| Можно изменять | ✓ | ✗ | ✓ |
| Повторы | ✓ | ✓ | ✗ |
| Типичное использование | Коллекция, которая растёт и меняется | Запись с фиксированной структурой | Уникальность и проверка принадлежности |

## Типичная ошибка

**Ошибка 1: tuple из одного элемента без запятой.**

```python
colors = ("red")
print(type(colors))
```

```text
<class 'str'>
```

Одни скобки — просто арифметические скобки, как `(2 + 3)`. Чтобы создать tuple из одного элемента: `("red",)`. **tuple создаёт запятая**, а не скобки.

**Ошибка 2: `{}` как пустое множество.**

```python
seen = {}
seen.add("dana")
```

```text
AttributeError: 'dict' object has no attribute 'add'
```

`{}` — пустой **dict**. Пустое множество = `set()`.

**Ошибка 3: ожидать порядок или индекс в set.**

```python
names = {"dana", "avi"}
print(names[0])
```

```text
TypeError: 'set' object is not subscriptable
```

У set нет «первого». Если нужен порядок, превратите его в отсортированный список: `sorted(names)`.

**Ошибка 4: распаковка с несовпадающим числом переменных.**

```python
name, age = ("Dana", 24, "Haifa")
```

```text
ValueError: too many values to unpack (expected 2)
```

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Практика

Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.

### Упражнение 1: Распаковка

```python
student = ("Dana", 24, "Haifa")
```

Распакуйте tuple в три переменные одной строкой и напечатайте о них предложение. Затем попробуйте изменить возраст на 25 — что произойдёт?

**Ожидаемый вывод:**

```text
Dana is 24 and lives in Haifa
```

{{< solution >}}
```python
student = ("Dana", 24, "Haifa")

name, age, city = student
print(name, "is", age, "and lives in", city)
```

```text
Dana is 24 and lives in Haifa
```

`student[1] = 25` выбросит `TypeError: 'tuple' object does not support item assignment` — tuple **неизменяем** (Immutable). Если нужно его изменить, создайте новый tuple: `student = (name, 25, city)`.
{{< /solution >}}

### Упражнение 2: Функция, которая возвращает два значения

Напишите функцию `min_max(numbers)`, которая возвращает tuple из наименьшего и наибольшего. Распакуйте результат в две переменные.

```python
lowest, highest = min_max([40, 3, 99, 17])
print(lowest, highest)
```

**Ожидаемый вывод:**

```text
3 99
```

{{< solution >}}
```python
def min_max(numbers):
    return (min(numbers), max(numbers))


lowest, highest = min_max([40, 3, 99, 17])
print(lowest, highest)
```

```text
3 99
```

Функция всегда возвращает **одно** значение — здесь tuple. Распаковка на стороне вызывающего кода превращает его в две переменные.
{{< /solution >}}

### Упражнение 3: Уникальные посетители

```python
visits = ["dana", "avi", "dana", "noa", "avi", "dana"]
```

Напечатайте, сколько было просмотров, сколько было **разных** посетителей и заходил ли `"tom"`.

**Ожидаемый вывод:**

```text
6 views
3 visitors
False
```

{{< solution >}}
```python
visits = ["dana", "avi", "dana", "noa", "avi", "dana"]

unique = set(visits)
print(len(visits), "views")
print(len(unique), "visitors")
print("tom" in unique)
```

```text
6 views
3 visitors
False
```

`set` не хранит повторов, поэтому `len(set(...))` считает, сколько разных значений. Проверка `in` для множества к тому же очень быстрая.
{{< /solution >}}

### Упражнение 4: Операции над множествами

```python
football = {"dana", "avi", "noa", "tom"}
chess = {"noa", "rina", "tom", "yossi"}
```

1. Кто состоит в обоих кружках?
2. Кто только в футболе?
3. Сколько всего разных детей?

Напечатайте ответы 1 и 2 отсортированными, чтобы порядок был фиксированным.

**Ожидаемый вывод:**

```text
['noa', 'tom']
['avi', 'dana']
6
```

{{< solution >}}
```python
football = {"dana", "avi", "noa", "tom"}
chess = {"noa", "rina", "tom", "yossi"}

print(sorted(football & chess))
print(sorted(football - chess))
print(len(football | chess))
```

```text
['noa', 'tom']
['avi', 'dana']
6
```

- `&` — пересечение (в обоих). `-` — разность (только в первом). `|` — объединение (хотя бы в одном из них).
- У множества нет фиксированного порядка, поэтому `sorted` возвращает отсортированный список для печати.
{{< /solution >}}

### Упражнение 5: list, tuple или set?

Для каждого случая — какую структуру вы бы использовали? Объясните.

1. Координаты точки на карте: широта и долгота.
2. Список задач, в который вы добавляете и из которого удаляете, по порядку.
3. Адреса электронной почты всех зарегистрировавшихся — без повторов.
4. Очередь на кассе в супермаркете.

{{< solution >}}
1. **tuple** — фиксированная запись из двух значений, которые не должны меняться: `(32.08, 34.78)`.
2. **list** — порядок важен, и вы добавляете и удаляете.
3. **set** — без повторов и с быстрой проверкой, есть ли уже такой адрес.
4. **list** — порядок важен, и берут с начала (`pop(0)`).
{{< /solution >}}

<!-- exercises:end -->

## Итоги

- **tuple** `( , )` — упорядоченный, **нельзя изменить**. Подходит для записи с фиксированной структурой. Один элемент: `(x,)`.
- **Распаковка**: `a, b = (1, 2)`. Так возвращают и получают несколько значений из функции.
- **set** `{ }` — без повторов, без порядка, без индекса. Пустое множество = `set()`.
- `set(list)` убирает повторы. `in` для множества очень быстрый.
- Операции над множествами: `|` объединение, `&` пересечение, `-` разность.
- В темах бэкенда и баз данных строки из базы данных будут возвращаться как **tuple**.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Официальная документация

- [Python Tutorial — Tuples and Sequences](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences)
- [Python Tutorial — Sets](https://docs.python.org/3/tutorial/datastructures.html#sets)
- [Built-in Types — Set Types](https://docs.python.org/3/builtins/stdtypes.html#set-types-set-frozenset)

<!-- docs:end -->
