EduBrick
классная работа

Множества и уникальность

Программирование на Python: от нуля до олимпиад

0/18решено 0 из 18до зачёта осталось 10

Чему научитесь

  • Убирать повторы одним действием
  • Проверять вхождение мгновенно вместо прохода по списку
  • Пользоваться объединением, пересечением и разностью
  • Выбирать между списком, словарём и множеством по задаче

Как устроено занятие

Сначала разбор с примерами и короткими проверками понимания — они нужны, чтобы поймать непонятое сразу, а не через три темы.

Дальше 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).

Потеря повторов там, где они нужны. Превратили список в множество — и «сколько раз встретилось» уже не спросить. Если нужны частоты, множества мало.

Порядок в цикле. В задачах вида «есть ли пара с суммой xx» число добавляют в множество после проверки. Иначе элемент найдёт сам себя и ответ окажется неверным при xx, равном удвоенному значению.

Как проверять себя

  • все элементы одинаковые — множество из одного элемента;
  • все элементы разные — размер совпадает с длиной списка;
  • пустое пересечение — что выводится по условию;
  • пара из одного элемента — не считает ли программа элемент парой самому себе.
теория

Практика: пятнадцать задач

Лестница прежняя: пять разминочных, семь основных, три со звёздочкой. Зачёт при десяти решённых.

Часть задач решается одной операцией над множествами — это нормально: смысл в том, чтобы узнать нужную операцию, а не написать её вручную.

В задачах со звёздочкой множество работает иначе: как память о просмотренном. Скользящее окно без повторов, цепочка последовательных чисел, поиск зацикливания — везде оно отвечает на вопрос «это уже было?» мгновенно.

лестница задач
развёрнутый ответ

Пара, которая нашла сама себя

Задача: «даны список чисел и число xx; есть ли в списке два элемента с разными номерами, сумма которых равна xx

Ученик написал:

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 и x=10x = 10 программа отвечает YES, хотя пары нет. Объясните, почему так вышло, и исправьте.

Войдите, чтобы ответить.