Множина: типи set, frozensent
Множини в Python представлені типами set
та frozenset
і використовуються для збереження неупорядкованих колекцій унікальних елементів. На відміну від списків і кортежів, множина автоматично видаляє дублікати та не гарантує порядок елементів.
У цьому розділі:
Коли використовувати set
?
Множини слід використовувати тоді, коли:
- потрібно зберігати унікальні значення,
- неважливий порядок елементів,
- потрібно швидко перевірити наявність елемента (
x in my_set
), - потрібно прибрати дублікати зі списку,
- ви виконуєте математичні операції над множинами: об'єднання, перетин, різницю тощо.
Уявіть множину як коробку, у якій елементи не мають позиції, і кожен елемент зʼявляється лише один раз. Як набір ключів: порядок неважливий, але жоден ключ не дублюється.
Створення множини
Множина задається за допомогою фігурних дужок {...}
або функції set()
:
s = {1, 2, 3}
print(s) # {1, 2, 3}
empty = set() # порожня множина
print(type(empty)) # <class 'set'>
⚠️ Увага! {}
— це порожній словник (тип dict
), а не множина. Щоб створити порожню множину, використовуйте set()
.
v1 = {}
print(type(v1)) # <class 'dict'>
Python автоматично видаляє дублікати:
a = {1, 2, 2, 3, 3, 3}
print(a) # {1, 2, 3}
Можна створити множину з інших типів, Python також автоматично прибере дублікати:
chars = set("hello")
print(chars) # {'h', 'e', 'l', 'o'} — `l` лише один раз.
s1 = set([1, 2, 2, 3, 3, 3])
print(s1) # {1, 2, 3}
s2 = set([1, 2, 2, 3, 3, 3])
print(s2) # {1, 2, 3}
🤔 Чому set("hello")
повертає елементи у різному порядку?
Множина (set
) у Python — це неупорядкована структура даних. Це означає, що порядок елементів у множині не визначається та не гарантується. Коли ви викликаєте set("hello")
, Python створює множину унікальних символів: {'h', 'e', 'l', 'o'}
. Проте порядок виводу цих символів при друці залежить від внутрішньої хеш-таблиці, яку Python використовує для зберігання множин.
Навіть якщо вміст той самий, порядок може змінюватися:
print(set("hello")) # {'h', 'e', 'l', 'o'}
print(set("hello")) # {'o', 'h', 'l', 'e'}
Це не помилка і не випадковість — це особливість множин у Python. Якщо вам важливо зберегти порядок, використовуйте список або кортеж (які зберігають порядок елементів), або перетворіть множину у список та відсортуйте:
print(sorted(set("hello"))) # ['e', 'h', 'l', 'o']
Таким чином, set("hello")
завжди міститиме ті самі символи, але їх порядок при виведенні може змінюватися — як при перегляді вмісту коробки, у якій елементи перемішуються при кожному відкритті.
Основні операції
Уявіть, що s = {1, 2, 3}
:
Операція | Опис | Приклад | Результат |
---|---|---|---|
len() |
Кількість елементів у множині, повертається ціле | len({1, 2, 3}) |
3 |
in |
Перевірка наявності елемента, повертається значення типу bool |
2 in {1, 2, 3} |
True |
add(x) |
Додає значення x до множини (обʼєкт множини змінюється в памʼяті) |
s.add(5) |
{1, 2, 3, 5} |
remove(x) |
Видаляє значення x , якщо існує, інакше — помилка. Обʼєкт множини змінюється в памʼяті. |
s.remove(2) |
{1, 3} |
discard(x) |
Видаляє значення x , якщо існує (без помилки). Обʼєкт множини змінюється в памʼяті. |
s.discard(100) |
Без змін |
clear() |
Очищає множину. Обʼєкт множини змінюється в памʼяті. | s.clear() |
set() |
s = {1, 2, 3}
s.add(4)
s.remove(2)
print(s) # {1, 3, 4}
Python пропонує два способи видалити елемент з множини: методи remove()
та discard()
. Якщо ви спробуєте видалити елемент, якого немає у множині, за допомогою remove()
, виникне помилка KeyError
, і виконання програми буде перерване:
s = {1, 2, 3}
s.remove(10) # KeyError: 10
print(s)
Натомість метод discard()
працює безпечно: якщо елемент у множині є — він буде видалений, а якщо немає — Python просто проігнорує запит без помилки:
s = {1, 2, 3}
s.discard(10) # Нічого не станеться, помилки немає
print(s)
Тобто discard()
— це мʼякий спосіб видалення, а remove()
— суворий. Уявіть, що remove()
— це вимогливий охоронець: якщо шуканого немає, він здіймає тривогу. А discard()
— спокійний працівник: не знайшов — просто пішов далі.
Множинні операції
Множини особливо корисні завдяки підтримці математичних операцій над множинами:
Операція | Python-синтаксис | Опис | Приклад | Результат |
---|---|---|---|---|
Обʼєднання | a | b або a.union(b) |
Елементи, які є в a або b |
{1, 2} | {2, 3} |
{1, 2, 3} |
Перетин | a & b або a.intersection(b) |
Елементи, які є і в a , і в b |
{1, 2} & {2, 3} |
{2} |
Різниця | a - b або a.difference(b) |
Елементи, які є в a , але не в b |
{1, 2, 3} - {2} |
{1, 3} |
Симетрична різниця | a ^ b або a.symmetric_difference(b) |
Елементи, які є в a або b , але не в обох |
{1, 2} ^ {2, 3} |
{1, 3} |
a = {1, 2, 3}
b = {2, 3, 4}
print(a | b) # {1, 2, 3, 4}
print(a & b) # {2, 3}
print(a - b) # {1}
print(a ^ b) # {1, 4}
Ці операції не змінюють оригінальні множини — вони повертають нову множину.
Порівняння множин
Усі операції повертають значення типу bool
: True
або False
:
Операція | Опис | Приклад | Результат |
---|---|---|---|
a <= b |
a є підмножиною b |
{1, 2} <= {1, 2, 3} |
True |
a < b |
a — істинна підмножина b |
{1, 2} < {1, 2, 3} |
True |
a >= b |
a містить b |
{1, 2, 3} >= {2} |
True |
a == b |
множини однакові за вмістом | {1, 2} == {2, 1} |
True |
Конвертація у list
або tuple
Якщо вам потрібна впорядкованість — конвертуйте множину у список або кортеж:
s = {3, 1, 2}
lst = list(s)
tpl = tuple(s)
print(lst) # [1, 2, 3] (порядок не гарантується)
❗Увага! Порядок елементів у множині випадковий, тому list(set(...))
часто використовують для видалення дублікатів зі списку, не зберігаючи порядок.
Множини та зміна
Множини — змінюваний тип (mutable), тому можна додавати або видаляти елементи. Але є також незмінний варіант множини — frozenset
. Він поводиться як звичайна множина, але не підтримує методи add()
, remove()
тощо.
fs = frozenset([1, 2, 3])
print(2 in fs) # True
fs.add(4) # AttributeError
Практичне завдання: пошук унікальних та спільних користувачів
У нас є два списки користувачів:
registered_users
— список усіх зареєстрованих користувачів на сайті.active_users
— список користувачів, які сьогодні заходили на сайт.
Завдання:
- Виведіть користувачів, які сьогодні були активні (спільні для обох списків).
- Виведіть користувачів, які зареєстровані, але сьогодні не заходили.
- Виведіть користувачів, які сьогодні заходили, але не зареєстровані (можливо, гість або бот).
- Виведіть всі унікальні імена користувачів, які згадуються у будь-якому зі списків.
Рішення:
registered_users = {"anna", "olena", "ivan", "petro", "serhii"}
active_users = {"ivan", "serhii", "bot42", "olena"}
# 1. Спільні користувачі (і зареєстровані, і активні)
both = registered_users & active_users
print("Активні зареєстровані:", both, len(both))
# 2. Зареєстровані, але сьогодні не заходили
inactive = registered_users - active_users
print("Зареєстровані, але неактивні:", inactive, len(inactive))
# 3. Активні, але не зареєстровані
unregistered = active_users - registered_users
print("Активні незареєстровані:", unregistered, len(unregistered))
# 4. Усі унікальні згадані імена
all_users = registered_users | active_users
print("Усі унікальні користувачі:", all_users, len(all_users))