Множина: типи 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)) #
⚠️ Увага! {} — це порожній словник (тип dict), а не множина. Щоб створити порожню множину, використовуйте set().
v1 = {}
print(type(v1)) #
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))