← все задачи
Python · задача 4 из 10
Дубликаты и нарезка на пачки
Начальный
10–15 минут
множествагенераторыitertools
Условие
Первое: уберите дубликаты из списка, сохранив порядок первого появления. Второе: напишите функцию, которая режет любой итерируемый объект на пачки по N элементов — например, чтобы отправлять их в API батчами.
Что требуется
- Дедупликация — за один проход
- Нарезка работает с любым итерируемым, включая генератор и файл
- Последняя пачка может быть неполной
Пример
dedup([3, 1, 3, 2, 1]) # [3, 1, 2] list(chunked([1, 2, 3, 4, 5], 2)) # [[1, 2], [3, 4], [5]]
Сначала уточните
Вопросы до кода — половина оценки. Молча начать печатать хуже, чем задать два вопроса.
- Элементы хешируемые? Если это словари — множество не подойдёт
- Нужен список на выходе или достаточно генератора?
- Что делать при size <= 0 — падать или вернуть пусто?
Показать решение Скрыть решение
Решение
from itertools import islice
def dedup(items):
seen = set()
result = []
for item in items:
if item not in seen:
seen.add(item)
result.append(item)
return result
def chunked(iterable, size):
if size <= 0:
raise ValueError("size must be positive")
iterator = iter(iterable)
while True:
batch = list(islice(iterator, size))
if not batch:
return
yield batch
# Python 3.12+: то же самое есть в стандартной библиотеке
# from itertools import batched
Почему так
Почему set, а не «item not in result»
- Проверка вхождения в список — O(n), и весь цикл превращается в O(n²): на 100 000 элементов это уже минуты
- Множество даёт O(1) на проверку ценой памяти — классический размен, который здесь и проверяют
- list(dict.fromkeys(items)) — однострочник с тем же эффектом: порядок сохраняется с Python 3.7
Почему islice, а не срезы по индексам
- Срез items[i:i + size] требует список — генератор или файл так не нарезать, а именно ими обычно и приходят данные
- islice тянет ровно size элементов из любого итератора и не читает лишнего
- Решение через yield не держит все пачки в памяти: обработали батч — забыли
Почему iter(iterable) в начале
- islice от самого списка каждый раз начинала бы сначала — получился бы бесконечный цикл из одной и той же пачки
- Явный iter() фиксирует позицию чтения между итерациями
- Это любимая ловушка в этой задаче: если написать без iter, всё «работает», пока не передадут список
Что спросят дальше
- Нехешируемые элементы: для словарей ключом делают кортеж нужных полей или json.dumps(sort_keys=True)
- Спросят про дедупликацию по полю: seen.add(item["id"]) — и функция становится dedup_by(items, key)
- Спросят, знаете ли itertools.batched из 3.12 и что делать на старых версиях
Следующая задача
Слить пересекающиеся интервалы — Задача про бронирования и расписания: единственный алгоритмический сюжет, который правда встречается в бэкенде.