📦 Репозиторий занятия 34

Урок 34. Словари, frozenset

Как работать с репозиторием

Каждая карточка говорит, о чём файл, что он выводит и что в нём искать. Код виден прямо здесь: его можно скопировать одной кнопкой, скачать файл или открыть его целиком.

Маршрут изучения

  1. Прочитайте описание: по нему уже понятно, о чём файл и что он выведет.
  2. Предскажите вывод: сравните своё предположение со строкой «Что выводит».
  3. Запустите: скачайте файл или скопируйте код кнопкой и выполните его у себя.
  4. Измените: поменяйте одно условие или значение и объясните новый результат.

Файлы: рекомендуемый порядок

1
MarkdownРазбор концепции86 строк

frozenset: неизменяемое множество

less_18__frozenset__dict_introduction/theory_01__frozenset.md

Вводит `frozenset` как неизменяемую версию `set`: элементы нельзя добавить, удалить или изменить после создания. Даёт таблицу способов создания, таблицу свойств (неизменяемость, но при этом хэшируемость — в отличие от обычного `set`) и таблицу методов, из которой явно исключены мутирующие (`add`, `remove`, `discard`, `clear`). Итоговая сравнительная таблица `set` vs `frozenset` — по изменяемости, хэшируемости, возможности быть ключом словаря или элементом другого множества, набору методов и производительности. Отдельно разобрано правило: тип результата операции (`|`, `.union()` и т.п.) между `set` и `frozenset` определяется типом левого операнда.

  • frozenset() и frozenset(range(3)) — два способа создания
  • Таблица свойств: неизменяемость + хэшируемость (в отличие от set)
  • Методы только неизменяющие: union, intersection, difference, symmetric_difference, issubset, issuperset, isdisjoint, copy
  • Сравнительная таблица set vs frozenset: изменяемость, хэшируемость, годность как ключ dict / элемент set
  • immutable_set.union({4, 5}) → frozenset({...}), а {4,5}.union(immutable_set) → обычный set
  • Правило: тип результата определяется типом объекта СЛЕВА от оператора/метода
Показать начало файла (86 строк всего)
## `frozenset`

 — это **неизменяемое (immutable)** множество в Python. 

В отличие от обычного `set`, его элементы нельзя изменять после создания:  
нельзя добавлять, удалять или изменять элементы.


## Способы создания

| Способ                                              | Пример                 | Результат                 |
|:----------------------------------------------------| :--------------------- |:--------------------------|
| Пустое множество                                    | `frozenset()`          | `frozenset()`             |
| Из любого `iterable` <br>с неизменяемыми элементами | `frozenset(range(3))`  | `frozenset({0, 1, 2})`    |


## Свойства

| Свойство                    | Описание                                                           |
| :-------------------------- | :----------------------------------------------------------------- |
| **Неизменяемость**          | После создания нельзя добавить или удалить элементы                |
| **Хэшируемость**            | Можно использовать как ключ в `dict` или элемент другого множества |
| **Порядок не сохраняется**  | Как и в `set`, элементы неупорядочены                              |
| **Не допускает дубликатов** | Повторяющиеся элементы удаляются автоматически                     |



## Методы

Методы, доступные у `frozenset`, — только **неизменяющие** операции над множествами:

| Метод                         | Описание                               | Пример                            |
| :---------------------------- | :------------------------------------- | :-------------------------------- |
| `copy()`                      | Возвращает копию множества             | `fs.copy()`                       |
| `union(*others)`              | Объединение                            | `fs.union({3, 4})`                |
| `intersection(*others)`       | Пересечение                            | `fs.intersection({2, 3})`         |
| `difference(*others)`         | Разность                               | `fs.difference({1})`              |
| `symmetric_difference(other)` | Симметрическая разность                | `fs.symmetric_difference({3, 4})` |
| `issubset(other)`             | Проверка, является ли подмножеством    | `fs.issubset({1, 2, 3})`          |
| `issuperset(other)`           | Проверка, является ли надмножеством    | `fs.issuperset({1})`              |
| `isdisjoint(other)`           | Проверка, не пересекаются ли множества | `fs.isdisjoint({5, 6})`           |

Методов `add()`, `remove()`, `discard()`, `clear()` — ОТСУТСТВУЮТ.



## Сравнение `set` и `frozenset`

| Критерий                                | `set`                           | `frozenset`             |
| :-------------------------------------- |:--------------------------------|:------------------------|
| Изменяемость                            | ✅ изменяемый                    | ❌ неизменяемый          |
| Хэшируемость                            | ❌ нет                           | ✅ да                    |
| Можно быть элементом множества          | ❌ нет                           | ✅ да                    |
| Можно быть ключом словаря               | ❌ нет                           | ✅ да                    |
| Методы изменения (`add`, `remove`)      | ✅ есть                          | ❌ нет                   |
| Методы объединения, пересечения и т. д. | ✅ есть                          | ✅ есть                  |
| Производительность                      | немного медленнее `frozenset`'a | немного быстрее `set`'a |

---

…
Проверьте себя: Почему `frozenset` может быть ключом словаря или элементом множества, а обычный `set` — нет?
2
MarkdownРазбор концепции195 строк

dict: устройство, методы и правило «ключ — всё, значение — ничто»

less_18__frozenset__dict_introduction/theory_02__dict.md

Базовый конспект по словарю: определение через хэш-таблицу пар ключ–значение, доступ по ключу через `[]` (кидает `KeyError`) и через `.get(key, default)` (безопасный вариант). Отдельно разобран неочевидный кейс с дубликатами ключей по хэшу — `{1.0: "float", 1: "integer", True: "boolean"}` схлопывается в один ключ `1.0`, потому что `1 == 1.0 == True` по хэшу. Дальше — таблицы способов создания, свойств, полный список методов (`get`, `keys`, `values`, `items`, `pop`, `popitem`, `update`, `clear`, `copy`, `setdefault`), dict comprehension, и итоговое правило «ключ — всё, значение — ничто»: `in`, `del`, `for` и `len()` работают только по ключам.

  • person["name"] (KeyError на несуществующем) vs person.get("name", default)
  • {1.0: "float", 1: "integer", True: "boolean"} → схлопывается в {1.0: 'boolean'} — общий хэш
  • Таблица методов: get, keys, values, items, pop, popitem, update, clear, copy, setdefault
  • dict comprehension: {x: x**2 for x in range(5)} и с фильтром if x % 2 == 0
  • Правило «ключ — всё, значение — ничто»: in/del/for/len работают по ключам, не по значениям
  • Обход значений и пар: .values() и .items()
Показать начало файла (195 строк всего)
## Словарь `dict`

— это неупорядоченная (до Python3.7!) изменяемая коллекция пар **ключ–значение**,
где каждому **уникальному** `hasible` ключу соответствует **одно** значение.

В основе словаря лежит хэш-таблица.

```python
person = {"name": "Alice", "age": 25}
```

* `"name"`, `"age"` — ключи
* `"Alice"`, `25` — значения

## Получить значение по ключу

Доступ к значениям возможен по ключам:

```python
person = {"name": "Alice", "age": 25}

print(person["name"])  # Alice
```

Попытка получить значение по несуществующему ключу даёт ошибку:

```python
person = {"name": "Alice", "age": 25}

print(person["surname"])  # KeyError: 'surname'
```

Поэтому, удобнее получать значение с помощью метода `.get(key[, default])`:
* Если ключ есть, возвращает его значение
* Если нет, возвращает значение по дефолту (по умолчанию None) 

```python
person = {"name": "Alice", "age": 25}

print(person.get("name", "Bob"))  # "Alice"
print(person.get("surname"))  # None
```

## Добавить / изменить значение по ключу

```python
person = {"name": "Alice", "age": 25}
person["city"] = "Paris"
person["age"] = 26

print(person)  # {'name': 'Alice', 'age': 26, 'city': 'Paris'}
```

## Можно ли в словаре быть несколько одинаковых ключей?

Если у нескольких пар одинаковый ключ (точнее, одинаковый хэш!), то останется последняя пара:

```python
d = {1.0: "float", 1: "integer", True: "boolean"}
print(d)  # {1.0: 'boolean'}
…
Проверьте себя: Почему `{1.0: "float", 1: "integer", True: "boolean"}` превращается в словарь с одним ключом `1.0`, и почему остаётся именно значение `'boolean'`, а не `'float'`?
3
MarkdownРазбор концепции76 строк

Сортировка и разворот словаря через items → sorted → dict

less_18__frozenset__dict_introduction/theory_03__dict_sort_and_reverse.md

Показывает единственный рабочий алгоритм сортировки словаря в Python: разобрать его в список пар через `.items()`, отсортировать этот список функцией `sorted()`, собрать обратно в словарь через `dict()`. Даётся полная трёхшаговая версия и упрощённая однострочная (`dict(sorted(d.items()))`), сортировка по ключу и по значению (через `key=lambda x: str(x[1])`), а также разворот порядка через `reversed()` вместо `sorted()`. Отдельно подчёркнуто: до Python 3.7 понятия «сортировка словаря» не существовало вовсе, а для сложных случаев рекомендован `OrderedDict`.

  • Алгоритм: list(d.items()) → sorted() → dict(...)
  • Однострочный вариант: dict(sorted(d.items()))
  • Сортировка по ключу (по умолчанию) — sorted(d.items())
  • Сортировка по значению — sorted(d.items(), key=lambda x: str(x[1]))
  • Разворот порядка — dict(reversed(d.items())) вместо sorted()
  • До Python 3.7 сортировки словарей не существовало; для сложных случаев — OrderedDict
Показать начало файла (76 строк всего)
# Сортировка словарей

До версии Python 3.7 сам термин сортировки словарей отсутствовал!

Теперь можно, правда, с оговорками (в сложных сортировках может дать неожиданный результат).

Поэтому, для сложных и ответственных сортировок используйте `OrderedDict` (упорядоченный словарь)

## Алгоритм сортировки словаря

1. "Разбираем" словари на список пар ключ-значение
2. Сортируем этот список с помощью `sorted()`
3. Превращаем результат в словарь с помощью  `dict()` 

### Пример 1
Сортировка по ключу (по возрастанию)

```python
d = {"name": "Alice", "age": 25, "city": "Paris"}

# 1. "Разбираем" словари на список пар ключ-значение
list_of_pairs = list(d.items())
print(list_of_pairs) # [('name', 'Alice'), ('age', 25), ('city', 'Paris')]

# 2. Сортируем этот список с помощью `sorted()`  
sorted_list = sorted(list_of_pairs)
print(sorted_list)  # [('age', 25), ('city', 'Paris'), ('name', 'Alice')]

# 3. Превращаем результат в словарь с помощью  `dict()`
new_dict = dict(sorted_list)
print(new_dict)
# {'age': 25, 'city': 'Paris', 'name': 'Alice'}
```

### Пример 1.1.
Объект `dict_items` тоже сортируется, поэтому решение можно упростить (совместить этапы):

```python
d = {"name": "Alice", "age": 25, "city": "Paris"}

new_dict = dict(sorted(d.items()))
print(new_dict)
# {'age': 25, 'city': 'Paris', 'name': 'Alice'}
```

### Пример 2
Сортировка по значению (по возрастанию)

```python
d = {"name": "Alice", "age": 25, "city": "Paris"}

# 1. "Разбираем" словари на список пар ключ-значение
list_of_pairs = list(d.items())
print(list_of_pairs) # [('name', 'Alice'), ('age', 25), ('city', 'Paris')]

# 2. Сортируем этот список с помощью `sorted()`  
sorted_list = sorted(list_of_pairs, key=lambda x: str(x[1]))
print(sorted_list)  # [('age', 25), ('city', 'Paris'), ('name', 'Alice')]

# 3. Превращаем результат в словарь с помощью  `dict()`
…
Проверьте себя: Почему нельзя просто вызвать `sorted(d)` без `.items()`, если нужно отсортировать словарь по значению, а не по ключу?