Множества (set)

Еще один специальный тип для работы со списками в Python называется множества или на английском set. Его особенностью является, то что в таком списке хранятся только уникальные элементы. Точнее вы можете добавлять в список любое количество неуникальных объектов, но в результате останутся только по одному уникальному элементу. Это свойство делает set'ы полезными для использования в задачах связанных с операциями над множествами. Если последняя фраза не вызвала у вас никаких чувств, то не переживайте. Сейчас мы со всем разберемся.

Для того чтобы создать новый set используются фигурные скобки:

>>> avengers = {'Железный Человек', 'Капитан Америка', 'Тор', }
>>> avengers
{'Капитан Америка', 'Железный Человек', 'Тор'}

Set можно создавать из любого входящего множества с помощью конструктора:

>>> set("абракадабра")
{'к', 'а', 'р', 'д', 'б'}

В сет можно добавлять элементы разных типов:

>>> x = {42, 'строка', 3.14159, None}
>>> x
{'строка', 42, 3.14159, None}

Можно добавлять даже другие типы оперирующие множествами:

>>> {42, 'строка', (1, 2, 3), 3.14159}
{'строка', 42, 3.14159, (1, 2, 3)}

Но нельзя добавлять списки, потому, что списи являются изменяемым типом:

>>> {[1, 2, 3]}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Для того чтобы добавить новые элементы существующего множества можно воспользоваться методами .add() и .update():

>>> avengers.add('Черная вдова')
>>> avengers.update({'Халк', 'Соколиный глаз'})
>>> avengers
{'Капитан Америка', 'Тор', 'Железный Человек', 'Халк', 'Соколиный глаз', 'Черная вдова'}

Для того чтобы удалить элементы есть два метода .remove() и .discard(). Отличаются тем, возвращается ли ошибка если элемент не найден:

>>> avengers.remove('Капитан Америка')
>>> avengers.remove('Капитан Америка')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'Капитан Америка'
>>> avengers.discard('Капитан Америка')

Так же есть уже знакомый по спискам метод .pop():

>>> x = avengers.pop()
>>> x
'Тор'
>>> avengers
{'Железный Человек', 'Халк', 'Соколиный глаз', 'Черная вдова'}

Для полной очистки так же используется метод .clear():

>>> avengers.clear()
>>> avengers
set()

Методы множества

Давайте рассмотрим методы которые доступны объектам типа set.

Методы модификации (доступны только для set и не доступны для frozenset):

Метод Описание
add() Добавление элементов в множество
remove() Удаление элемента из множества, если элемент не найден в момент удаления, то возникает исключение KeyError
clear() Удаление всех элементов множества
copy() Создание поверхностной копии множества
pop() Извлечение (возврат и удаление) элемента если множество уже пустое то возникает исключение KeyError
update() Обновление множества путем добавления всех переданных элементов
discard() Удаление элемента если он присутсвует в множестве, если элемент не найден, то ошибки не возникает

Методы и операторы для взаимодействий множеств

Основное назначние set — это работа с множествами в математическом смысле: поиск пересечения, объединение или поиск вхождений. С другой стороны используя set не доступны такие такие вещи как найти элемент по индексу или получить срез. Что делает set-ы уникальными.

Оператор Метод Описание
x | y, x |= y union() Возвращает множество элементов обоих множеств (объединяет)
x - y difference() Возвращает разницу между множествами, только уникальные элементы левого множества
x -= y difference_update() Обновляет x и удаляет из него все элементы множества y
x & y intersection() Возвращает пересечение множеств, те элементы которые есть и в x и в y
x &= y intersection_update() Обновляет x оставляя только те элементы которые есть и в x и y
x ^ y symmetric_difference() Возвращает симметричную разницу, оставляет только уникальные элементы обоих множеств и удаляет те которые были общими
x ^= y symmetric_difference_update() Обновляет x симметричной разницей, то есть оставляет только элементы из обоих множеств которые не повторялись
x.isdisjoint(y) Возвращает True если оба множества не имеют одинаковых элементов (нулевое пересечение)
x <= y x.issubset(y) Возвращает True если все элементы множества x содержатся в y (x является подмножеством y)
x < y Возвращает True если все элементы множества x содержатся в y и множества не идентичны (x является собственным подмножеством y)
x >= y x.issuperset(y)> Возвращает True если все элементы y содержатся в x (x является надмножеством y)
x > y Возвращает True если все элементы y содержатся в x и множества не идентичны (x является надмножеством y)

Помните в начале урока я говорил, что не страшно если вы не помните о математических операциях над множествами. Потому что графическое представление существенно облегчает понимание. Если вы сталкиватесь в первый раз с операциями над множествами, то достаточно запомнить, графическое представление по смыслу и потом уже вспомнить нужный метод.

Объединение (union)

set union

Все уникальные элементы обоих множеств добавляются в новое множество.

>>> x = {1, 2, 3}
>>> y = {3, 4, 5}
>>> x | y
{1, 2, 3, 4, 5}
>>> x.union(y)
{1, 2, 3, 4, 5}

Или с использованием операции присваивания:

>>> x |= y
>>> x
{1, 2, 3, 4, 5}

Операция может работать и с большим количеством элементов:

>>> x = {1, 2, 3}
>>> y = {3, 4, 5}
>>> z = {3, 5, 6, 7}
>>> x | y | z
{1, 2, 3, 4, 5, 6, 7}
>>> x.union(y, z)
{1, 2, 3, 4, 5, 6, 7}

Пересечение (intersection)

set intersection

Поиск пересечения множеств это поиск элементов которые входят в оба (или больше) множеств.

>>> x = {1, 2, 3}
>>> y = {3, 4, 5}
>>> x & y
{3}

И можно использовать больше чем два множества для проведения операций:

>>> z = {3, 5, 6, 7}
>>> x & y & z
{3}

Или то же самое используя методы:

>>> x.intersection(y)
{3}
>>> x.intersection(y, z)
{3}

Если надо обновить множество, то можно использовать оператор присваивания:

>>> x &= y
>>> x
{3}

Разница множеств (difference)

set difference

Используется тогда когда вам надо получить все элементы множества которые не пересекаются с другим множеством.

Используя оператор:

>>> x = {1, 2, 3}
>>> y = {3, 4, 5}
>>> x - y
{1, 2}

Или метод:

>>> x.difference(y)
{1, 2}

Так же работает со несколькими множествами:

>>> z = {3, 5, 6, 7}
>>> x - y - z
{1, 2}
>>> x.difference(y, z)
{1, 2}

Или способ с оператором присвоения:

>>> x -= y
>>> x
{1, 2}

Симметричная разница (symmetric difference)

set symmetric difference

Симметричная разница позволяет получить элементы которые не повторяются в сравниваемых множествах.

>>> x = {1, 2, 3}
>>> y = {3, 4, 5}
z = {3, 5, 6, 7}
>>> x ^ y
{1, 2, 4, 5}
>>> x ^ y ^ z
{1, 2, 3, 4, 6, 7}

С использованием метода множества:

>>> x.symmetric_difference(y)
{1, 2, 4, 5}

И с присваиванием:

>>> x ^= y
>>> x
{1, 2, 4, 5}

Неизменяемые множества (frozenset)

Если для списков есть неизменяемая (иммутабельная) альтернатива в виде кортежей, то для множеств set есть иммутабельное альтер-эго frozenset. Создать frozenset можно только через конструктор:

>>> guardians = frozenset(['Звёздный Лорд', 'Гамора', 'Грут', 'Ракета', 'Дракс'])
>>> guardians.add('Ронан')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'
>>> guardians.pop()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'pop'
>>> guardians.clear()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'clear'

Переменная против неизменяемого объекта

Обратите внимание, что иммутабельность не то же самое, что и невозможность переопределить переменную. Например мы хотим получить множество которое содержит только элементы которые входят в два множества используя оператор пересечения &=:

>>> f = frozenset([1, 2, 3, 4, 5])
>>> s = {2, 4, 6}
>>> f &= s
>>> f
frozenset({2, 4})

Тут на самом деле происходит следующее:

  • Мы создали две переменные f и s
  • Операция &= то же самое, что и f = f & s, или по другому можно записать как f = f.intersection(s)
  • В результате выполнения операции f.intersection(s) возвращается новый frozenset и он присваивается той же переменной, что и была использована до этого

Это легко проследить если отследить изменяется ли значение id:

>>> f = frozenset([1, 2, 3, 4, 5])
>>> id(f)
4475977800
>>> f &= s
>>> id(f)
4471590696

В действительности неизменяемые типы не дают возможности себя модифицировать, но мы можем тому же самому идентификатору присвоить новое значение.

Применение множеств

Самая распространенная задача когда у вас есть список и надо получить из него только уникальные элементы без повторений. Можно сделать сложную проверку каждого элемента, но гораздо проще написать такой код:

>>> a = [1, 6, 3, 4, 1, 5, 5, 5, 2, 3, 4,]
>>> b = set(a)
>>> b
{1, 2, 3, 4, 5, 6}

Конечно, это работает только в том случае если элементы списка хешируемы.

Ссылки