Практикум 8 собирает вместе всё, что изучено в уроке 30 (comprehension, стек/очередь) и уроке 32 (множества). Примеры ниже проходят через все 13 задач source по возрастанию сложности: сначала comprehension с фильтром, затем параллельная обработка списков, затем матрицы, множества и очередь. Весь вывод в комментариях получен запуском кода — включая место, где порядок вывода не гарантирован, это отмечено отдельно.
Пример 1. List comprehension с фильтром — короткая замена циклу с if
Задачи 1 и 2 practicum'а — это один и тот же паттерн: пройти по коллекции и оставить только то, что подходит под условие.
end = 20
squares = [number ** 2 for number in range(1, end + 1) if number % 2 == 0]
print(squares)
# [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]
words = ["apple", "cherry", "kiwi", "banana", "orange"]
excluded_char = "r"
filtered_words = [word for word in words if excluded_char not in word]
print(filtered_words)
# ['apple', 'kiwi', 'banana']
Что происходит: if в конце comprehension работает как фильтр — элемент попадает в результат, только если условие истинно. Форма читается «для каждого числа из диапазона, если оно чётное, взять его квадрат».
Пример 2. zip(): обработка двух параллельных списков одновременно
Когда данные о каждой сущности разбросаны по нескольким спискам одинаковой длины (как animals и weights), zip() соединяет их поэлементно, не заводя отдельный класс или словарь.
animals = ["тигр", "слон", "обезьяна", "змея"]
weights = [250, 4000, 15, 5]
descriptions = [f"{animal.capitalize()} весит {weight} кг" for animal, weight in zip(animals, weights)]
lightest_animal = animals[weights.index(min(weights))]
print(descriptions)
# ['Тигр весит 250 кг', 'Слон весит 4000 кг', 'Обезьяна весит 15 кг', 'Змея весит 5 кг']
print("Самое лёгкое животное:", lightest_animal) # змея
Почему так: weights.index(min(weights)) находит позицию минимального веса, а та же позиция в animals — само животное. Это работает, только пока оба списка идут в одном порядке и без пропусков — стоит спискам «разъехаться», и результат станет неверным без единой ошибки Python.
Пример 3. Группировка через set длин и sorted()
Задача 4 — сгруппировать слова по длине, самые длинные группы первыми, а внутри группы — по алфавиту. Собственный set здесь нужен не для слов, а для списка уникальных длин.
words = ["apple", "banana", "kiwi", "cherry", "pear", "grape", "melon"]
groups = []
for length in sorted({len(word) for word in words}, reverse=True):
group = sorted([word for word in words if len(word) == length])
groups.append(group)
print("Группы слов:", groups)
# [['banana', 'cherry'], ['apple', 'grape', 'melon'], ['kiwi', 'pear']]
Что происходит: {len(word) for word in words} — это set comprehension, оно сразу убирает повторяющиеся длины. sorted(..., reverse=True) задаёт порядок групп, а внутренний sorted() — порядок слов внутри каждой группы. В уроке 34 та же задача решается короче — словарём, где ключ — длина слова.
Пример 4. Вложенный comprehension: развернуть матрицу в плоский список
Comprehension умеет заменять не только один for, но и вложенные циклы — порядок for в выражении такой же, как если бы вы писали их друг под другом.
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [value for row in matrix for value in row]
print(flat) # [1, 2, 3, 4, 5, 6]
Как проверить себя: разверните запись в обычный цикл — for row in matrix: for value in row: flat.append(value). Порядок for в comprehension копирует порядок вложенности циклов слева направо.
Пример 5. Координатная сетка: comprehension с двумя независимыми for
В отличие от примера 4, здесь второй for не зависит от первого — оба перебирают один и тот же диапазон, и результат — все пары координат n × n клетки.
n = 3
grid = [(row, col) for row in range(n) for col in range(n)]
print(grid)
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
print(len(grid)) # 9 — n * n координат
Что происходит: для каждого row из внешнего for перебирается весь диапазон col — получается декартово произведение. Число элементов результата всегда n * n, а не n + n, как иногда ожидают по аналогии с примером 4.
Пример 6. all() + range(n): проверить условие сразу для всех индексов
Задача 7 сравнивает две диагонали матрицы. Вместо ручного цикла с флагом ok = True удобнее — генераторное выражение внутри all(), которое останавливается на первом несовпадении.
matrix1 = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
matrix2 = [
[1, 0, 3],
[0, 5, 0],
[7, 0, 9],
]
n = len(matrix1)
main_equal = all(matrix1[i][i] == matrix2[i][i] for i in range(n))
secondary_equal = all(matrix1[i][n - 1 - i] == matrix2[i][n - 1 - i] for i in range(n))
print("Совпадают ли главные диагонали:", main_equal) # True
print("Совпадают ли побочные диагонали:", secondary_equal) # True
Почему так: главная диагональ — это элементы [i][i], побочная — [i][n - 1 - i] (индекс столбца «отражён» относительно конца строки). Спутать эти две формулы — самая частая ошибка задачи, отдельно отмеченная в разделе «Ошибки».
Пример 7. Set comprehension: подсчёт уникальных слов без учёта регистра
Задача 8 просит посчитать уникальные слова, но "Apple" и "apple" должны считаться одним и тем же словом — значит, регистр нужно привести к одному виду до того, как слова попадут в множество.
text = "Apple orange apple banana Orange"
unique_words = {word.lower() for word in text.split()}
print("Количество уникальных:", len(unique_words)) # 3
print(sorted(unique_words)) # ['apple', 'banana', 'orange']
Что происходит: text.split() без аргумента режет строку по пробелам, {word.lower() for word in ...} — set comprehension: та же идея, что и list comprehension, но результат — множество, где повторы (после приведения регистра) схлопываются сами.
Пример 8. Сравнение через set теряет количество повторов
Задача 9 — проверить, «состоят ли два списка из одинаковых цифр». Здесь легко ошибиться с тем, что именно сравнивается: набор уникальных значений или список целиком, вместе с повторами.
list1 = [1, 2, 3, 4, 4]
list2 = [4, 3, 2, 1, 1]
print(set(list1) == set(list2)) # True — одинаковый набор чисел
print(sorted(list1) == sorted(list2)) # False — но количество повторов разное
set([1, 1, 2]) == set([1, 2, 2]) тоже даёт True — множество хранит только сам факт наличия значения, а не сколько раз оно встретилось. Если по условию задачи важно и количество повторов, сравнивайте отсортированные списки (или, забегая вперёд, collections.Counter), а не множества.
Пример 9. Операторы множеств вместо ручных циклов: - & | ^
Задачи 10 и 11 «Охота за сокровищами» и «Уникальные в множестве» — учебник по четырём операторам множеств. Каждый оператор напрямую переводит формулировку условия в код, без единого for.
chest1 = {"золото", "серебро", "рубины", "алмазы"}
chest2 = {"серебро", "рубины", "изумруды", "сапфиры"}
print("Только в первом сундуке:", sorted(chest1 - chest2)) # разность
print("Общее в обоих сундуках:", sorted(chest1 & chest2)) # пересечение
print("Все уникальные драгоценности:", sorted(chest1 | chest2)) # объединение
set1 = {1, 2, 3}
set2 = {3, 4, 5}
print("Только в одном из множеств:", sorted(set1 ^ set2)) # симметрическая разность
# [1, 2, 4, 5] — то, что есть ровно в одном из двух множеств
Что происходит: - оставляет то, что есть слева, но нет справа; & — общее; | — всё вместе без повторов; ^ (симметрическая разность, за рамки прямого source добавлена как парная к первым трём) — то, что попало ровно в одно множество из двух, но не в оба сразу.
^ не встречается в задачах source явно, он логично дополняет тройку - & |. Сверьтесь с docs.python.org — Set Types.
Пример 10. Уникальные значения с сохранением порядка появления
Задача 12: обычный set(numbers) уберёт дубликаты, но порядок появления не гарантирует — сюда же ведёт предупреждение «Ошибки» про «ожидать порядок там, где его нет». Правильный приём — set для быстрой проверки «уже видели?» плюс список для результата.
numbers = [4, 1, 7, 6, 2, 6, 8, 1, 5, 4]
seen = set()
unique_numbers = []
for number in numbers:
if number not in seen:
seen.add(number)
unique_numbers.append(number)
print("Уникальные по порядку:", unique_numbers) # [4, 1, 7, 6, 2, 8, 5]
print("Через set() теряется порядок:", set(numbers)) # {1, 2, 4, 5, 6, 7, 8} — у вас может быть другой порядок печати
Почему так: seen используется только для проверки number not in seen — эта операция у множества выполняется быстро независимо от размера. Порядок в конечном результате задаёт список unique_numbers, а не множество.
Пример 11. Очередь заказов: deque вместо списка
Последняя задача — обработка очереди FIFO («первым пришёл — первым ушёл»). Список тоже умеет это через pop(0), но каждый такой вызов сдвигает все оставшиеся элементы; deque устроен так, чтобы забирать и добавлять элементы с обоих концов без сдвига.
from collections import deque
orders = deque(["Pizza", "Burger", "Pasta"])
first_order = orders.popleft()
print("Первый заказ:", first_order) # Pizza
print(f"Осталось {len(orders)} заказов:") # Осталось 2 заказов:
for order in orders:
print("-", order)
# - Burger
# - Pasta
orders.append("Sushi") # новый заказ встаёт в конец очереди
print("После нового заказа:", list(orders)) # ['Burger', 'Pasta', 'Sushi']
# то же самое обычным списком — работает, но менее выразительно для очереди
orders_list = ["Pizza", "Burger", "Pasta"]
first_via_list = orders_list.pop(0)
print("То же самое списком:", first_via_list, orders_list) # Pizza ['Burger', 'Pasta']
deque.popleft() и list.pop(0) выходит за рамки source. Сверьтесь с docs.python.org — collections.deque.
Что дальше: словарные версии группировки (пример 3) и подсчёта разберёт урок 34 — там же появится frozenset как неизменяемая версия множества из примеров 7–10.