Практикум закрепляет выбор между set, dict и frozenset на десяти задачах. Примеры идут от отдельных приёмов (comprehension, пересечение множеств, проверка подмножества) к их совмещению в задачах — группировке по неизменяемому набору. Весь вывод в комментариях получен запуском кода.
Пример 1. Задание 1 — числа, кратные 3 или 5
numbers = [16, 18, 1, 6, 3, 2, 6, 2, 14, 3, 20, 15, 19, 4, 18, 15, 15, 4, 20, 18]
result = {number for number in numbers if number % 3 == 0 or number % 5 == 0}
print(result) # {3, 6, 15, 18, 20}
print(len(numbers), len(result)) # 20 5
Что происходит: set comprehension делает две вещи сразу — фильтрует по условию (% 3 == 0 or % 5 == 0) и убирает дубликаты, потому что set не хранит повторы. В исходном списке 20 чисел с повторами, в результате — 5 уникальных значений, кратных 3 или 5.
Пример 2. Задание 2 — общие ключи двух словарей
dict1 = {"a": 1, "b": 2, "c": 3}
dict2 = {"b": 5, "d": 7, "a": 8}
common_keys = set(dict1) & set(dict2)
print(sorted(common_keys)) # ['a', 'b']
if common_keys:
print(", ".join(sorted(common_keys))) # a, b
else:
print("-")
dict3 = {"x": 1}
common_none = set(dict1) & set(dict3)
print(common_none if common_none else "-") # -
Что происходит: set(dict1) даёт множество ключей словаря (значения при этом теряются — для поиска общих ключей они и не нужны); & — пересечение множеств. Когда общих ключей нет, set() получается пустым — а пустой set при проверке в if ложен, поэтому нужен явный запасной вариант "-".
Пример 3. Задание 3 — список строк в словарь длин
words = ["apple", "banana", "cherry", "date"]
lengths = {word: len(word) for word in words}
print(lengths) # {'apple': 5, 'banana': 6, 'cherry': 6, 'date': 4}
Что происходит: dict comprehension {ключ: значение for элемент in коллекция} — прямой аналог list comprehension, но строит пары. Здесь ключом стало само слово, значением — его длина; повторяющихся слов в списке нет, поэтому пар ровно столько же, сколько элементов.
Пример 4. Задание 4 — проверка подмножества словаря
dict_a = {"a": 1, "b": 2}
dict_b = {"a": 1, "b": 2, "c": 3}
is_subset = all(item in dict_b.items() for item in dict_a.items())
print(is_subset) # True
dict_c = {"a": 1, "b": 99}
is_subset2 = all(item in dict_b.items() for item in dict_c.items())
print(is_subset2) # False — ключи те же, а значение "b" другое
Что происходит: .items() возвращает пары (ключ, значение), и in сравнивает пары целиком — значит, недостаточно, чтобы ключи dict_a были в dict_b, нужно совпадение и значений (ошибка №1 из топ-3). У dict_c ключи совпадают, но "b": 99 отличается от "b": 2 — проверка справедливо возвращает False.
Пример 5. Задание 5 — удаление пустых значений
data = {"a": None, "b": 2, "c": "", "d": [], "e": [1, 2], "f": 0}
cleaned = {key: value for key, value in data.items() if value}
print(cleaned) # {'b': 2, 'e': [1, 2]}
Что происходит: if value в comprehension проверяет «истинность» значения — ложны None, "", [] и, что легко упустить, число 0. Здесь "f": 0 тоже удалился вместе с явными пустышками, хотя формально 0 — не то же самое, что «нет значения». Если 0 в вашей задаче значим (например, счётчик), фильтровать нужно явно — if value is not None and value != "", а не полагаться на общую истинность.
Пример 6. Задание 6 — потерянные страницы книги
book = {1: "Начало истории", 2: None, 3: "Глава 1", 4: None, 5: "Глава 2"}
fixed_book = {
page: (text if text is not None else "Страница потеряна")
for page, text in book.items()
}
print(fixed_book)
# {1: 'Начало истории', 2: 'Страница потеряна', 3: 'Глава 1', 4: 'Страница потеряна', 5: 'Глава 2'}
Что происходит: здесь проверка — уже не общая истинность, как в примере 5, а точное сравнение is not None. Это важно: текст главы теоретически мог бы быть пустой строкой "" (тоже ложное значение), но задача просит заменить только явное отсутствие данных — None, а не любое пустое значение.
Пример 7. Задание 7 — вложенный словарь оценок
grades = {
"anna": [5, 4, 3, 5],
"bennet": [3, 2, 4],
"john": [5, 5, 5],
}
report = {}
for student, marks in grades.items():
report[student] = {
"оценки": marks,
"средний балл": sum(marks) / len(marks),
}
print(report)
# {'anna': {'оценки': [5, 4, 3, 5], 'средний балл': 4.25},
# 'bennet': {'оценки': [3, 2, 4], 'средний балл': 3.0},
# 'john': {'оценки': [5, 5, 5], 'средний балл': 5.0}}
Что происходит: значением внешнего словаря стал ещё один словарь — так строится вложенная структура, привычная по уроку 36. Средний балл — обычное sum(marks) / len(marks); для более сложной вложенности (профиль ученика с оценками по нескольким предметам) та же идея просто добавляет ещё один уровень словарей.
Пример 8. Задание 8 — рецепты по набору ингредиентов
recipes = {
("flour", "egg", "milk"): "Pancakes",
("milk", "flour", "egg"): "Waffles",
("sugar", "butter"): "Icing",
}
grouped_recipes = {}
for ingredients, recipe in recipes.items():
grouped_recipes.setdefault(frozenset(ingredients), []).append(recipe)
print(grouped_recipes[frozenset(["egg", "milk", "flour"])])
# ['Pancakes', 'Waffles']
available = {"egg", "milk", "flour", "sugar"}
for ingredients_needed, recipe_names in grouped_recipes.items():
if ingredients_needed.issubset(available):
print(f"Можно приготовить: {recipe_names} из {sorted(ingredients_needed)}")
# Можно приготовить: ['Pancakes', 'Waffles'] из ['egg', 'flour', 'milk']
Что происходит: ключи исходного словаря — кортежи с ингредиентами в разном порядке (("flour", "egg", "milk") и ("milk", "flour", "egg")); если группировать прямо по кортежу, «Pancakes» и «Waffles» попали бы в разные группы, хотя набор один и тот же (ошибка №2 из топ-3). frozenset(ingredients) убирает порядок, и оба рецепта объединяются под одним ключом. Второй фрагмент показывает практическое применение: issubset() находит рецепты, для которых хватает доступных продуктов — «Icing» не попал в вывод, потому что «butter» нет в available.
Пример 9. Задание 9 — студенты с одинаковым набором предметов
students = {
"Alice": ["Math", "Physics"],
"Bob": ["Math", "Physics"],
"Charlie": ["Chemistry", "Biology"],
"David": ["Math", "Physics"],
"Eve": ["Chemistry", "Biology"],
}
groups = {}
for student, subjects in students.items():
groups.setdefault(frozenset(subjects), []).append(student)
for subjects, names in groups.items():
print(f"Группа с предметами: {', '.join(sorted(subjects))}: {names}")
# Группа с предметами: Math, Physics: ['Alice', 'Bob', 'David']
# Группа с предметами: Biology, Chemistry: ['Charlie', 'Eve']
Что происходит: тот же приём, что и в примере 8 — frozenset как ключ группировки, только источник данных другой (списки предметов у студентов). setdefault(key, []).append(...) экономит проверку «есть ли уже такой ключ» — то же самое, что defaultdict(list) из урока 41, только без импорта.
Пример 10. Задание 10 — перевод слова с предложением добавить новое
english_words = {"cat": "кот", "dog": "собака", "bird": "птица"}
def translate(word, dictionary):
if word in dictionary:
return dictionary[word]
return f"'{word}' нет в словаре — добавьте перевод"
print(translate("cat", english_words)) # кот
print(translate("fish", english_words)) # 'fish' нет в словаре — добавьте перевод
english_words["fish"] = "рыба"
print(translate("fish", english_words)) # рыба
Что происходит: здесь достаточно обычного dict — задача не про уникальность или неизменяемость набора, а про поиск по ключу с понятным сообщением при промахе. После явного добавления пары english_words["fish"] = "рыба" тот же вызов находит перевод — словарь изменяемый, в отличие от frozenset из примеров 8–9.
Пример 11. Почему frozenset, а не set, годится в ключ словаря
try:
bad = {{"egg", "milk"}: "pancakes"}
except TypeError as error:
print(f"TypeError: {error}")
# TypeError: unhashable type: 'set'
good = {frozenset(["egg", "milk"]): "pancakes"}
print(good) # {frozenset({'egg', 'milk'}): 'pancakes'}
# — порядок элементов внутри frozenset не гарантирован, у вас может быть другим
print(frozenset(["egg", "milk"]) == frozenset(["milk", "egg"])) # True
Что происходит: ключ словаря обязан быть хешируемым, а хешируемость требует неизменяемости — set можно менять после создания (add, remove), поэтому Python запрещает его как ключ (ошибка №3 из топ-3). frozenset — тот же набор уникальных значений, но замороженный: у него нет изменяющих методов, зато есть хеш, и, как показано в примерах 8–9, два frozenset с одинаковыми элементами в разном порядке равны друг другу (хотя порядок печати элементов внутри самого frozenset при этом не фиксирован).
Что дальше
Все десять задач практикума — в заданиях. Если результат разошёлся с ожидаемым — сверьтесь с типичными ошибками и разбором в решениях.