💻 Примеры кода

⚡ Шпаргалка практикума

numbers = [4, 1, 7, 6, 2, 6, 8, 1, 5, 4]
seen = set()
unique = []
for n in numbers:
    if n not in seen:
        seen.add(n)
        unique.append(n)
print(unique)   # [4, 1, 7, 6, 2, 8, 5]
ИнструментКогда использовать
comprehension + ifпостроить список/множество с фильтром за одну строку
zip()пройти параллельно по двум спискам одинаковой длины
set и - & | ^уникальность, пересечение, объединение, разность/симметрия
all() + range(n)проверить условие сразу для всех индексов
dequeочередь FIFO: popleft()/append() без сдвига списка

Топ-3 ошибки: путать ссылку и копию; ожидать порядок там, где его нет; усложнять выражение, когда обычный цикл читается лучше.

Практикум 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.