
Множества
Синтаксис. Изменение множеств. Операции над множествами. Методы объектов множеств
Штана Альберт Игоревич
Использование множеств
Ключи словаря хранятся в нем в единственном экземпляре. Добавление нового значения по существующему ключу заменяет старое значение. Хранение в единственном экземпляре полезно и в тех случаях, когда нам нужно хранить не столько значения по ключам, сколько именно сами ключи.
Например, нужно хранить список городов, которые посетил каждый пользователь. При повторном посещении города дублировать запись не требуется. Это позволяет сэкономить память и упростить поиск информации. Также нам может понадобиться узнать, какие города посетили и Вася и Маша, а какие — только Маша или только Вася. По сути, это хранения перечня элементов в неких наборах и сопоставления этих наборов между собой. В математике для решения такого рода задач служат множества. В свою очередь, Python предоставляет одноименную структуру данных — set. Итак, множества в Python — это неупорядоченные последовательности элементов, каждый из которых в множестве представлен ровно один раз.
Создание множеств и манипуляции над ними
Множество можно создать с помощью соответствующего литерала:
s = {1, 2, 3, 2, 1}
s # {1, 2, 3}
type(s) # <class 'set'>
Литералы множеств записываются в фигурных скобках, как и литералы словарей.
Однако внутри скобок через запятую перечисляются только элементы множества.
Литерал {} уже занят словарями, поэтому пустое множество создается вызовом функции set без аргументов:
set() # {}
type(set()) # <class 'set'>
Эту же функцию можно использовать, чтобы создать множество из элементов произвольного количества итераторов или итерируемых элементов:
set("abracadabra") # {'c', 'd', 'a', 'r', 'b'}
set([1, 2, 3, 2, 1]) # {1, 2, 3}
set({"a": 1, "b": "2"}) # {'a', 'b'}
Заметьте, что в множестве каждый уникальный элемент представлен ровно один раз, даже если в коллекции-источнике были повторы.
Проверка на вхождение
Для некоторых задач нужно проверять, является ли некое значение элементом множества — другими словами, «входит ли оно в множество» или «принадлежит ли оно множеству». В таких случаях нужно использовать оператор in:
42 in set() # False
42 in set([42]) # True
"a" in set("abracadabra") # True
Изменение состава элементов множества
Множества в Python — изменяемые. Добавлять и удалять элементы из множества можно с помощью методов add, discard и remove:
s = set()
s.add(1)
s.add(2)
s.add(2)
s # {1, 2}
s.discard(1)
s # {2}
s.discard(1)
s # {2}
s.remove(1) # KeyError: 1
При чтении кода в этом примере вы должны были заметить, что добавление лишних элементов с помощью add и отбрасывание несуществующих элементов с помощью discard не приводят к ошибке.
Однако вызов метода remove с несуществующим элементом приводит к ошибке.
Копирование и очистка множеств
Множества изменяемы, поэтому требуется сделать копию перед изменением оригинала.
Как и словари, множества не поддерживают операцию получения среза.
Для копирования приходится использовать метод copy, создающий поверхностную копию множества:
s1 = {1, 2, 3}
s2 = s1.copy()
s1 is s2 # False
s1 == s2 # True
s2.add(4)
s1 == s2 # False
s2 # {1, 2, 3, 4}
Очистить множество без пересоздания можно с помощью метода clear:
s = set("foobar")
s # {'f', 'a', 'r', 'o', 'b'}
Операции над множествами
Если при изучении множеств остановиться на создании и модифицировании, может показаться, что множества не сильно-то и отличаются от списков. Кажется, что они просто позволяют быстрее проверить вхождение элемента, но при этом не поддерживают механизм срезов.
Проверка на равенство
Сопоставление множеств — это довольно мощный инструмент. Давайте проверим два множества на равенство:
set([1, 2, 3, 2, 1]) == {3, 1, 2} # True
Можно подумать, что два множества равны, если каждый отдельный элемент одного множества содержится и во втором. Эта догадка близка к истине, но вспомним, что коллекции в Python хранят только ссылки на объекты. Множества равны, если ссылаются на одни и те же объекты. Одинаковые ссылки равны, но при этом могут быть равны и разные объекты. Дело в том, что в Python есть специальный протокол проверки на равенство. Большинство встроенных типов данных поддерживает этот протокол. Мы можем проверять на равенство числа, строки, булевы значения. А еще можем приравнивать кортежи, списки, словари. Здесь Python поступает очень разумно. Если вы приравняете две коллекции одного типа, то эти коллекции будут считаться равными, если их элементы попарно равны с точки зрения протокола. Посмотрите:
[1, 2, ["foo", "bar"]] == [1, 2, ["foo"] + ["bar"]] # True
(1, True, []) == (1, True, []) # True
{"a": 1, "b": 2} == {"b": 2, "a": 1} # True
Словари равны, если порядок ключей разный — лишь бы были равны значения по соответствующим ключам и сами наборы ключей были одинаковыми. Вот и множества равны, если содержат одинаковые наборы равных попарно элементов.
Объединение множеств
По аналогии с множествами в математике, множества в Python поддерживают операцию объединения (union). Эта операция не объединяет множества, а возвращает новый объект. Этот объект — это такое множество, которое содержит все элементы, содержащиеся хотя бы в одном из оригинальных множеств. По смыслу объединение похоже на операцию "ИЛИ" из булевой логики: элемент будет присутствовать в объединении, если он присутствует в первом исходном множестве ИЛИ во втором. Так это выглядит на схеме:

Для объединения множеств в Python используется оператор |:
visited_by_masha = {"Paris", "London"}
visited_by_kolya = {"Moscow", "Paris"}
visited_by_kolya | visited_by_masha # {'London', 'Moscow', 'Paris'}
Пересечение множеств
Еще есть «операция И» — пересечение множеств (intersection). В пересечение входят элементы, присутствующие в первом из оригинальных множеств И во втором:

В Python оператор пересечения — &:
visited_by_masha = {"Paris", "London"}
visited_by_kolya = {"Moscow", "Paris"}
visited_by_kolya & visited_by_masha # {'Paris'}
Разность множеств
Разность множеств (difference) — такое множество, элементы которого содержатся в первом оригинальном множестве, но не содержатся во втором. Разность представлена оператором -, потому что по смыслу оператор похож на вычитание из арифметики:
visited_by_masha = {"Paris", "London"}
visited_by_kolya = {"Moscow", "Paris"}
visited_by_masha - visited_by_kolya # {'London'}
visited_by_kolya - visited_by_masha # {'Moscow'}
Так разность можно обозначить на схеме:

Симметрическая разность
Симметрическая разность (symmetric difference) — множество, в которое входят элементы, присутствующие ЛИБО в первом, ЛИБО во втором оригинальном множестве:

По смыслу операция похожа на исключающее ИЛИ (xor), поэтому и представлена оператором ^:
visited_by_masha = {"Paris", "London"}
visited_by_kolya = {"Moscow", "Paris"}
visited_by_kolya ^ visited_by_masha # {'London', 'Moscow'}
Подмножества и надмножества
Одно множество является подмножеством другого (subset), если все элементы первого входят во второе, но второе может содержать еще и другие элементы. Второе в этом случае является надмножеством для первого (superset):

При этом равные множества являются друг для друга одновременно и подмножествами и надмножествами.
В Python соотношение множеств можно проверить с помощью методов issubset и issuperset:
a = {1, 2, 3, 4}
b = {3, 4}
a.issubset(b) # False
Методы объектов множеств
Существуют операторы, которые позволяют различными способами комбинировать множества. Эти операторы максимально похожи на те, что применяются в теории множеств в математике.
Операции над множествами как методы
С теорией множеств программисты обычно знакомы, хотя бы поверхностно. Поэтому множества нужно использовать в сочетании с операторами. Однако было бы неправильно умолчать, что у каждого оператора есть свой словесный метод-аналог. Познакомимся с этими методами-аналогами:
a.union(b) # аналог "a | b"
a.intersection(b) # аналог "a & b"
a.difference(b) # аналог "a - b"
a.symmetric_difference(b) # аналог "a ^ b"
Обновление множеств
Вспомним метод update у словаря, который обновляет словарь «по месту» с помощью данных из другого словаря.
Так вот, для множеств существует несколько таких update-методов:
- difference_update
- intersection_update
- symmetric_difference_update
- update Рассмотрим их подробнее:
- Метод difference_update работает похоже на difference. Он удаляет из связанного множества все элементы, которые входят в множество-аргумент:
a, b = {1, 2}, {2, 3}
a.difference_update(b)
print(a) # {1}
- Метод intersection_update и его изменяющий аналог intersection. Он оставляет в связанном множестве только те элементы, которые входят и в множество-аргумент:
a, b = {1, 2}, {2, 3}
a.intersection_update(b)
print(a) # {1}
- Метод symmetric_difference_update и его изменяющий аналог symmetric_difference. Он добавляет в связанное множество элементы, которые есть только в множестве-аргументе. Также он удаляет элементы, которые есть в обоих множествах:
a, b = {1, 2}, {2, 3}
a.symmetric_difference_update(b)
print(a) # {1, 3}
- Метод update и его изменяющий аналог union. Он дополняет связанное множество отсутствующими элементами из множества-аргумента:
a, b = {1, 2}, {2, 3}
a.update(b)
print(a) # {1, 2, 3}
Попробуйте сами запустить код в окне ниже с интерпретатором Python и повторите примеры из статьи чтобы самим увидеть и понять как всё это работает. Для этого в ячейке с кодом нажмите клавиши на клавиатуре Shift+Enter или запустите код через кнопку Run по значку ▶.