Справочник по Python · модуль 4 · словари, множества и кортежи
Глава 4.7
Операции над
множествами
Объединение, пересечение и две разности отвечают на вопросы о двух наборах сразу — без единого цикла. Это главное, ради чего множества стоит выбирать: задачи вида «чего не хватает» и «что общего» решаются одной строкой.
- Категория
- Операции set
- Уровень
- Базовый → средний
- Связанные темы
- Множества, логика, сравнение наборов
- Зачем это знать
- Сравнивать наборы данных без циклов
§1Вопросы о двух наборах
Пусть у пользователя есть набор прав, а страница требует своего набора. Вопросы возникают одни и те же.
| Вопрос | Операция |
|---|---|
| что есть в обоих наборах | пересечение & |
| чего не хватает | разность - |
| что есть хоть где-то | объединение | |
| что различается | симметрическая разность ^ |
| хватает ли прав целиком | подмножество <= |
Каждый из этих вопросов можно решить циклом с проверками, но операция короче, быстрее и читается как формулировка задачи.
- элементы, попадающие в результат
- элементы, которые операция отбрасывает
Четыре операции отличаются только тем, какая часть картинки остаётся закрашенной. Дальше каждая разбирается отдельно, но к этой схеме полезно возвращаться: почти любой вопрос о двух наборах сводится к выбору области на ней.
§2Объединение
Объединение собирает всё, что есть хотя бы в одном из наборов, — повторы при этом исчезают сами.
backend = {"Python", "SQL"}
frontend = {"JavaScript", "HTML", "SQL"}
print(sorted(backend | frontend))
['HTML', 'JavaScript', 'Python', 'SQL']
A | B только в A + общие + только в B
§3Пересечение
Пересечение оставляет только то, что есть в обоих наборах.
active = {101, 102, 103}
subscribed = {102, 103, 104}
print(sorted(active & subscribed))
[102, 103]
Символ & выбран не случайно: он же означает логическое «и» в побитовых операциях. Пересечение — это «элемент в A и в B», объединение — «в A или в B».
§4Разность
Разность оставляет элементы левого набора, которых нет в правом.
required = {"read", "write", "delete"}
granted = {"read", "write"}
print(sorted(required - granted))
['delete']
Это, пожалуй, самая полезная операция на практике: «чего не хватает», «что удалилось», «какие файлы ещё не обработаны» — всё это разность.
§5Направление разности
В отличие от объединения и пересечения, разность несимметрична: порядок операндов меняет смысл.
required = {"read", "write", "delete"}
granted = {"read", "write"}
print(required - granted)
print(granted - required)
{'delete'}
set()Первая строка отвечает «чего не хватает», вторая — «что выдано сверх нужного». Здесь лишних прав нет, поэтому результат пуст. Перепутать операнды местами легко, и ошибка будет тихой: результат окажется правдоподобным, но ответит не на тот вопрос.
§6Симметрическая разность
Эта операция оставляет то, что принадлежит ровно одному из наборов, — то есть всё, кроме общего.
yesterday = {"A", "B", "C"}
today = {"B", "C", "D"}
print(sorted(yesterday ^ today))
['A', 'D']
Результат отвечает «что различается», но не говорит, в какую сторону: A и D здесь равноправны. Если направление важно, берут две обычные разности.
A = {A, B, C} B = {B, C, D}
A | B → A B C D всё
A & B → B C общее
A - B → A только слева
A ^ B → A D только с одной стороны
§7Методы и операторы
У каждой операции есть два вида записи.
| Операция | Метод | Оператор |
|---|---|---|
| объединение | a.union(b) | a | b |
| пересечение | a.intersection(b) | a & b |
| разность | a.difference(b) | a - b |
| симметрическая разность | a.symmetric_difference(b) | a ^ b |
Результат одинаковый — выбирают по читаемости. Операторы короче, методы понятнее тем, кто видит код впервые.
§8Разница между ними
Одно отличие всё-таки есть, и оно практическое: метод принимает любую итерируемую коллекцию, оператор — только множество.
print({1, 2} | [3])
TypeError: unsupported operand type(s) for |: 'set' and 'list'
print(sorted({1, 2}.union([3])))
[1, 2, 3]
Отсюда правило: если один из наборов — список, либо преобразуйте его в множество, либо используйте метод.
§9Подмножество
Проверка «все ли элементы A входят в B» отвечает на вопрос вида «хватает ли прав».
required = {"read", "write"}
granted = {"read", "write", "delete"}
print(required.issubset(granted))
print(required <= granted)
True True
Запись required <= granted читается как «требуемое не больше выданного». Это гораздо короче, чем цикл с проверкой каждого права.
§10Надмножество
Зеркальная проверка — «содержит ли B всё из A».
required = {"read", "write"}
granted = {"read", "write", "delete"}
print(granted.issuperset(required))
print(granted >= required)
True True
Выбор между issubset() и issuperset() — вопрос того, что удобнее поставить в начало фразы.
§11Строгие и нестрогие проверки
Знаки < и <= различаются так же, как у чисел: строгий вариант требует, чтобы множества не были равны.
values = {"read", "write"}
print(values <= values)
print(values < values)
True False
| Запись | Значение | Равные множества |
|---|---|---|
a <= b, a.issubset(b) | A входит в B | True |
a < b | A входит в B и меньше него | False |
a >= b, a.issuperset(b) | A содержит B | True |
a == b | наборы совпадают | True |
В проверках прав почти всегда нужен нестрогий вариант: совпадение наборов — нормальная ситуация, а не отказ.
§12isdisjoint(): нет общего
Метод отвечает, что пересечение пусто, — и делает это, не строя само пересечение.
print({1, 2}.isdisjoint({3, 4})) print({1, 2}.isdisjoint({2, 3}))
True False
То же самое можно записать как not (a & b), но isdisjoint() останавливается на первом общем элементе и не создаёт промежуточное множество.
§13Несколько наборов сразу
Операции можно объединять в цепочку — или передать методу несколько аргументов.
a = {1, 2, 3}
b = {2, 3, 4}
c = {3, 4, 5}
print(sorted(a & b & c))
print(sorted(a.intersection(b, c)))
[3] [3]
Метод с несколькими аргументами удобнее, когда наборы лежат в списке: тогда их распаковывают звёздочкой из главы 6.6 — set.union(*groups).
§14Практика: права доступа
Собрать всё вместе на одной задаче.
granted = {"read", "write"}
required = {"write", "delete"}
print(required <= granted)
print(sorted(required - granted))
print(sorted(required & granted))
False ['delete'] ['write']
Первая строка отвечает «пускать или нет», вторая — «что именно запросить у администратора», третья — «что уже есть». Три цикла с условиями заменены тремя выражениями, каждое из которых читается как вопрос.
§15Частые ошибки
| Что написано | Что произойдёт | Как правильно |
|---|---|---|
granted - required вместо required - granted | ответ на другой вопрос, без ошибки | помнить: слева то, из чего вычитаем |
{1, 2} | [3] | TypeError: оператор требует множество | {1, 2}.union([3]) |
a ^ b для «что добавилось» | вернётся и добавленное, и исчезнувшее | b - a |
a < b для проверки прав | равные наборы дадут False | a <= b |
ждать, что a - b изменит a | операторы создают новое множество | методы с _update из главы 4.8 |
a and b вместо a & b | вернётся одно из множеств целиком | для пересечения нужен & |
сравнивать множества оператором > как размеры | это проверка надмножества, а не длины | len(a) > len(b) |
§16Проверьте себя
Какая операция несимметрична?
Разность: a - b и b - a отвечают на разные вопросы. Объединение, пересечение и симметрическая разность от порядка не зависят.
Чем a ^ b отличается от a - b?
Симметрическая разность даёт всё, что есть ровно в одном наборе, — с обеих сторон. Обычная разность — только то, что есть слева и нет справа.
Почему {1, 2} | [3] не работает, а union([3]) работает?
Оператор требует, чтобы оба операнда были множествами. Метод принимает любую итерируемую коллекцию и преобразует её сам.
Чем a < b отличается от a <= b?
Строгий вариант требует, чтобы множества не были равны: values < values даёт False.
Как проверить, что у наборов нет общих элементов?
a.isdisjoint(b). Это быстрее, чем строить пересечение и проверять его на пустоту.
Как одной строкой узнать, каких прав не хватает?
required - granted: элементы требуемого набора, которых нет в выданном.
§17Шпаргалка
a | ba & ba - ba ^ brequired <= granteda.isdisjoint(b)a.union(items)a.intersection(b, c)Итог главы
Четыре операции отвечают на вопросы о двух наборах без циклов: объединение даёт всё, пересечение — общее, разность — то, чего нет справа, симметрическая разность — то, что есть ровно с одной стороны. Разность единственная из них несимметрична, и перепутанный порядок операндов даёт правдоподобный ответ на другой вопрос. Проверки <= и >= отвечают «входит ли один набор в другой» — это готовая проверка достаточности прав. Операторы требуют множеств с обеих сторон, методы принимают любые коллекции.