Разность и симметричная разность множеств в Python
Разность отвечает на вопрос «что есть у первого и чего нет у второго», симметричная — «что есть ровно у одного из двух». Операторы - и ^, методы difference и symmetric_difference и почему порядок множеств важен.
Редакция Питоники
Объединение в уроке 6 собирало всё вместе, пересечение в уроке 7 — только общее. Остались два вопроса про пару множеств: что есть у одного и чего не хватает, и что есть ровно у одного из двух. Первое — разность, второе — симметричная разность. Обе постоянно нужны на данных: приглашённые гости минус пришедшие — кто не явился; все задачи минус сделанные — что осталось.
Разность: что есть у первого и нет у второго
Считаем, кто был приглашён, но не пришёл. Вычитание работает как в арифметике, только на элементах: из первого множества уходят те, кто нашёлся во втором:
invited = {'Анна', 'Борис', 'Вера', 'Глеб'}
came = {'Борис', 'Вера', 'Даша'}
no_show = invited - came
print(sorted(no_show))
print(len(no_show))
['Анна', 'Глеб'] 2
Множества печатаются через sorted — состав определён, а порядок вывода мы задаём сами. Тот же результат даёт метод difference — разница только в написании. Сравним их напрямую:
invited = {'Анна', 'Борис', 'Вера', 'Глеб'}
came = {'Борис', 'Вера', 'Даша'}
print(sorted(invited.difference(came)))
print(invited.difference(came) == invited - came)
['Анна', 'Глеб'] True
Порядок важен: a - b не равно b - a
Разность несимметрична: из первого множества вычитается второе, поэтому a - b и b - a — два разных результата. В арифметике 10 - 4 и 4 - 10 тоже разные — с множествами то же самое: поменять операнды местами нельзя без смены смысла. Возьмём план на день и уже сделанное:
planned = {'созвон', 'отчёт', 'ревью', 'пароль'}
done = {'отчёт', 'пароль'}
print(sorted(planned - done))
print(sorted(done - planned))
print(planned - done == done - planned)
['ревью', 'созвон'] [] False
Из плана ушли сделанные задачи — остались две нерешённые. В обратную сторону разность пуста: всё, что сделано, было и в плане. Это два разных вопроса: что осталось и что сделано сверх плана.
Симметричная разность: только у одного из двух
Обратный вопрос: кто есть ровно в одном из двух списков и не совпадает. В понедельник и в пятницу дежурили так:
monday = {'Анна', 'Борис', 'Вера'}
friday = {'Борис', 'Вера', 'Глеб'}
print(sorted(monday ^ friday))
print(sorted(monday.symmetric_difference(friday)))
print(monday ^ friday == friday ^ monday)
['Анна', 'Глеб'] ['Анна', 'Глеб'] True
Симметричная разность собрала Анну и Глеба: Анна дежурила только в понедельник, Глеб — только в пятницу. Борис и Вера были в обоих списках, поэтому в результат не попали. В отличие от обычной разности, симметричная коммутативна: a ^ b и b ^ a — одно и то же, порядок операндов не имеет значения. По смыслу это «или-или»: элемент попадает в результат, если есть в первом ИЛИ во втором, но не в обоих сразу.
Метод принимает список, оператор — нет
Операторы - и ^ требуют множества с обеих сторон: подсовать список нельзя. Методы же принимают любую коллекцию — список, кортеж, range. Это продолжение знакомой темы: одиночная проверка in из урока 5 тоже принимала что угодно:
stock = {'чай', 'кофе', 'сахар'}
order = ['чай', 'мёд']
print(sorted(stock.difference(order)))
['кофе', 'сахар']
stock = {'чай', 'кофе', 'сахар'}
order = ['чай', 'мёд']
print(stock - order)
Traceback (most recent call last):
File "app.py", line 4, in <module>
print(stock - order)
~~~~~~^~~~~~~
TypeError: unsupported operand type(s) for -: 'set' and 'list'Почему так: оператор — это синтаксис множеств, Python даже не пытается вычитать список; метод — обычный вызов, он терпеливо переберёт любую коллекцию. На практике выбирают единообразие: внутри одного проекта либо операторы, либо методы.
Вычесть на месте: -= и методы с update
Когда результат нужен прямо в первом множестве, работают изменяющие варианты: a -= b вычитает b из a, a ^= b оставляет в a только то, что есть ровно в одном из двух. Методы называются difference_update и symmetric_difference_update:
cart = {'хлеб', 'молоко', 'сыр', 'чай'}
sold_out = {'сыр', 'чай'}
cart -= sold_out
print(sorted(cart))
tags = {'новое', 'sale', 'top'}
fresh = {'sale', 'осень'}
tags ^= fresh
print(sorted(tags))
['молоко', 'хлеб'] ['top', 'новое', 'осень']
Корзина потеряла распроданные позиции, теги — общие с новой партией. Обратите внимание на сортировку: латинское top стоит перед кириллицей — сравниваются коды символов, а не языки. Методы с update меняют множество и возвращают None, поэтому пишутся отдельной строкой без присваивания: tags.symmetric_difference_update(fresh).
a = {1, 2, 3}
b = {3, 4}
print(sorted(a | b))
print(sorted(a & b))
print(sorted(a - b))
print(sorted(a ^ b))
[1, 2, 3, 4] [3] [1, 2] [1, 2, 4]
Четыре операции — четыре разных взгляда на пару множеств. Объединение и пересечение из двух прошлых уроков, разность и симметричная из этого — набор покрывает почти любой вопрос про состав.
| Запись | Метод | Что делает |
|---|---|---|
| a | b | a.union(b) | все элементы обоих множеств |
| a & b | a.intersection(b) | только общие элементы |
| a - b | a.difference(b) | элементы a без элементов b — порядок важен |
| a ^ b | a.symmetric_difference(b) | элементы ровно одного из двух |
| a -= b | a.difference_update(b) | вычесть b из a на месте |
| a ^= b | a.symmetric_difference_update(b) | оставить уникальное на месте |
Что дальше
Разность отвечает на вопрос «чего не хватает» — и это готовый инструмент для следующей темы. В уроке 9 проверяем «вкладывается ли один комплект в другой»: issubset и issuperset вернут True или False одной строкой — а разность там же покажет, чего именно не хватает.
Сначала предскажи ответ в голове — это главный навык программиста.
a = {1, 2, 3, 4}
b = {3, 4, 5}
print(sorted(a - b))
print(sorted(b - a))
print(len(a ^ b))
languages = {'py', 'js', 'go'}
known = {'py', 'go', 'rust'}
languages -= known
print(sorted(languages))
x = {'a', 'b'}
y = {'b', 'c'}
result = x ^ y
print(sorted(result))
print(len(x))
1. Что вернёт {'а', 'б', 'в'} - {'б'}?
2. Когда a - b совпадает с b - a?
3. Что соберёт a ^ b?
4. Что случится при вычислении stock - order, если order — список?
5. Чем difference_update отличается от difference?
6. Верно ли, что a ^ b равно b ^ a?
7. Выполнено cart -= sold_out. Что произошло с cart?
Даны множества all_files = {'report.txt', 'photo.jpg', 'notes.txt', 'data.csv'} и backed_up = {'photo.jpg', 'data.csv'}. Выведите два списка: файлы без резервной копии (разность) и файлы, которые есть только в одном из двух (симметричная разность) — оба через sorted.
Чем разность множеств отличается от remove и discard?
remove и discard удаляют один названный элемент, разность вычитает целую коллекцию и возвращает новое множество, не трогая исходники. Если нужно вычесть на месте — difference_update или оператор -=.
Урок 4: remove, discard и popПочему a - b и b - a дают разное?
Из первого множества вычитается второе: остаются элементы первого без элементов второго. При обмене операндов вопрос меняется на противоположный. Совпадают результаты только у равных множеств — обе разности тогда пусты.
Что такое симметричная разность и когда она нужна?
a ^ b собирает элементы, которые есть ровно в одном из двух множеств: расхождения между двумя списками, товары с разной ценой в двух магазинах. Метод — symmetric_difference; порядок операндов не важен.
Как вычесть список из множества?
Методом difference: он принимает любую коллекцию. Оператор - со списком упадёт с TypeError: unsupported operand type(s) for -. Осторожно со строками: метод разберёт их на отдельные буквы.
Почему a - b ничего не изменил в моём множестве?
Разность возвращает новое множество и исходное не трогает. Сохраните результат в переменную: diff = a - b — или вычтите на месте: a -= b.
Понравился урок? Сошлитесь на него
«Разность несимметрична: из первого множества вычитается второе, поэтому a - b и b - a — два разных результата.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Множества · Урок 6
Объединение множеств в Python: оператор | и union
Гости субботы плюс гости воскресенья — но каждый посчитан один раз. Оператор | и метод union собирают два множества в одно без дубликатов.
Множества · Урок 7
Пересечение множеств Python: оператор & и intersection
Общие элементы двух множеств одним знаком: & находит тех, кто есть в обоих. Пустое пересечение, isdisjoint и пересечение трёх наборов.
Множества · Урок 9
Подмножества в Python: issubset и issuperset
Один комплект внутри другого: issubset и issuperset возвращают True или False одной строкой. Строгие знаки < и >, пустое множество, списки вместо множеств и направления проверки.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
NumPy · Урок 4
Операции с массивами: векторизация и почему NumPy быстрее списков в 100 раз
Умножаем миллион цен одной строкой и замеряем время через time.perf_counter: почему векторизация NumPy обгоняет цикл for в десятки раз. Ufunc, np.dot, булевы сравнения и правило «никаких for там, где есть операция».
операции с массивами numpyпоэлементные операции numpy
NumPy · Урок 5
Broadcasting в NumPy: как складывать массивы разных размеров
Складываем вектор с матрицей без циклов: три правила broadcasting, столбцы через reshape и np.newaxis и честный разбор ошибки operands could not be broadcast together.
операции с массивами numpy
Множества · Урок 2
Создание множеств: {} и set() — литерал, список, строка
Два способа создать множество: литерал в фигурных скобках и set() от списка или строки. Пустые скобки — это словарь, поэтому пустое множество пишут set().
создание множества pythonмножества python