Чему научитесь
- Убирать повторы одним действием
- Проверять вхождение мгновенно вместо прохода по списку
- Пользоваться объединением, пересечением и разностью
- Выбирать между списком, словарём и множеством по задаче
Как устроено занятие
Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.
Дальше 15 задач лестницей: разминка, основа, со звёздочкой. Занятие засчитывается, когда решено 10 — остальные не пропадают и учитываются отдельно.
После занятия — вторая часть, ещё 15 задач на те же приёмы в новых сюжетах.
Сколько это займёт
Примерно час-полтора вместе с задачами. Сроков нет: можно закрыть вкладку и вернуться когда удобно — прогресс сохранится.
Набор без повторов
Словарь из прошлого занятия отвечал на вопрос «сколько раз». Но часто нужно другое — просто «встречалось или нет». Для этого есть множество.
values = set() # пустое множество
values.add(5)
values.add(3)
values.add(5) # уже есть — ничего не изменится
print(len(values)) # 2
print(5 in values) # True
Список можно превратить в множество одним действием, и повторы исчезнут сами:
a = [3, 8, 3, 1, 8]
print(len(set(a))) # 3 — сколько различных
print(sorted(set(a))) # [1, 3, 8]
Пустое множество записывается как set(), а не {} — фигурные скобки без содержимого означают пустой словарь. Это единственная неловкость в записи, дальше всё просто.
Множество похоже на словарь, у которого есть ключи и нет значений. Так оно, по сути, и устроено.
Чего у множества нет
Три вещи, которых ученик обычно ждёт от множества по привычке к спискам, — и не находит.
Нет порядка. Элементы хранятся так, как удобно самому Python. Печатать множество напрямую бессмысленно: порядок может оказаться каким угодно. Если нужен определённый — сортируйте: sorted(values).
Нет повторов. Добавить одно и то же дважды не выйдет, второй раз ничего не произойдёт. Это ровно то, за что множество и берут, — но если вопрос «сколько раз», нужен словарь.
Нет номеров. values[0] — ошибка. У элементов нет позиций, и «третий элемент множества» — бессмысленное словосочетание.
| нужно | что брать |
|---|---|
| порядок и повторы важны | список |
| сколько раз встретилось | словарь |
| только «есть или нет» и уникальность | множество |
Выбирать структуру по задаче — это и есть навык занятия. Множество не «лучше» списка, оно про другое.
Проверка: что получится
a = [4, 4, 2, 4, 2]
print(len(set(a)))
Зачем это нужно: скорость проверки
Главная причина, по которой множество берут, — не уникальность, а скорость проверки вхождения.
Запись x in a для списка выглядит коротко, но внутри это проход по всем элементам. Один раз — незаметно. А вот так — уже нет:
for x in queries: # сто тысяч запросов
if x in a: # проход по ста тысячам элементов
...
Десять миллиардов действий — решение не пройдёт. Замена одной строки всё меняет:
values = set(a) # один раз
for x in queries:
if x in values: # мгновенно, независимо от размера
...
Проверка в множестве не зависит от количества элементов: и в сотне, и в миллионе она занимает одно и то же время. Причина — то же устройство, что у словаря.
Правило: если проверка in оказалась внутри цикла, список надо заменить множеством. Одна строка превращает квадрат в линию, и это самый дешёвый способ ускорить решение из всех, что вы знаете.
Операции над множествами
Два множества можно сравнивать и соединять — привычными знаками.
first = {1, 2, 3}
second = {3, 4}
print(first | second) # {1, 2, 3, 4} — объединение
print(first & second) # {3} — пересечение
print(first - second) # {1, 2} — разность
print(first ^ second) # {1, 2, 4} — ровно в одном
| операция | смысл | словами |
|---|---|---|
a | b |
объединение | есть хотя бы в одном |
a & b |
пересечение | есть в обоих |
a - b |
разность | есть в первом, нет во втором |
a ^ b |
симметрическая разность | ровно в одном |
a <= b |
вложенность | всё из a есть в b |
Каждая из них заменяет цикл с проверками — и работает быстрее написанного вручную.
Операции можно соединять: first & second & third даёт значения, встречающиеся во всех трёх.
Проверка: размер объединения
В первом списке значения 1, 2, 2, 3. Во втором — 3, 4, 4.
Сколько элементов окажется в объединении множеств? Введите целое число.
Три ошибки этого занятия
Печать множества напрямую. print(values) выведет фигурные скобки и элементы в непредсказуемом порядке. Для ответа нужны sorted и print(*result).
Потеря повторов там, где они нужны. Превратили список в множество — и «сколько раз встретилось» уже не спросить. Если нужны частоты, множества мало.
Порядок в цикле. В задачах вида «есть ли пара с суммой » число добавляют в множество после проверки. Иначе элемент найдёт сам себя и ответ окажется неверным при , равном удвоенному значению.
Как проверять себя
- все элементы одинаковые — множество из одного элемента;
- все элементы разные — размер совпадает с длиной списка;
- пустое пересечение — что выводится по условию;
- пара из одного элемента — не считает ли программа элемент парой самому себе.
Практика: пятнадцать задач
Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.
Часть задач решается одной операцией над множествами — это нормально: смысл в том, чтобы узнать нужную операцию, а не написать её вручную.
В задачах со звёздочкой множество работает иначе: как память о просмотренном. Скользящее окно без повторов, цепочка последовательных чисел, поиск зацикливания — везде оно отвечает на вопрос «это уже было?» мгновенно.
Пара, которая нашла сама себя
Задача: «даны список чисел и число ; есть ли в списке два элемента с разными номерами, сумма которых равна ?»
Ученик написал:
n, x = map(int, input().split())
a = list(map(int, input().split()))
seen = set()
answer = "NO"
for value in a:
seen.add(value)
if x - value in seen:
answer = "YES"
break
print(answer)
На большинстве тестов ответ верный, но на входе из одного числа 5 и программа отвечает YES, хотя пары нет. Объясните, почему так вышло, и исправьте.