Что такое множество
Представьте гардероб в театре: номерки на вешалках. Два зрителя не могут получить один и тот же номерок — каждый номерок в гардеробе встречается ровно один раз. А в каком порядке номерки висят на крючках — никого не волнует, важно только одно: есть номерок в гардеробе или его там нет.
Именно так себя ведёт множество — в документации Python оно называется set. Это коллекция, где каждый элемент хранится только один раз и порядок элементов не имеет значения.
У множества три особенности, которые стоит запомнить сразу:
1. Уникальность — если попытаться положить в множество то, что там уже есть, ничего не изменится: дубликата не появится
2. Неупорядоченность — у элементов множества нет номера места, обратиться «к первому элементу» нельзя
3. Изменяемость — элементы можно добавлять и убирать уже после создания множества
Множество удобно там, где важен сам факт «это у нас уже есть» или «этого элемента здесь нет», а не порядок и не количество повторов. Например, чтобы быстро убрать повторяющиеся слова из списка или проверить, встречается ли имя в большом перечне.
Пустые фигурные скобки — это не множество
Здесь чаще всего ошибаются даже те, кто уже неплохо пишет на Python. Если раньше вы разбирали словари, то знаете, что фигурные скобки {} создают словарь. С множествами это создаёт путаницу: хочется по аналогии написать {} и получить пустое множество, но так не получится.
a = {}
print(type(a))
b = set()
print(type(b))
Разбираем построчно:
a = {}— пустые фигурные скобки без содержимого. Python читает это как пустой словарь, а не множество: так исторически устроен язык.print(type(a))— выводит тип переменнойa. На экране будет<class 'dict'>, то есть словарь.b = set()— вызов функцииset()без аргументов. Вот это и есть правильный способ создать пустое множество.print(type(b))— выведет<class 'set'>: теперь перед нами действительно множество.
{} без содержимого — всегда пустой словарь. Пустое множество создаётся только вызовом set().А если внутри фигурных скобок уже есть элементы, всё меняется: если между элементами нет пары «ключ: значение», это множество, а не словарь.
s = {1, 2, 3}
d = {"a": 1, "b": 2}
s = {1, 2, 3}— в фигурных скобках просто перечислены значения через запятую, без двоеточий. Это множество из трёх чисел.d = {"a": 1, "b": 2}— здесь у каждого элемента есть пара «ключ:значение». Это словарь.
Как создать множество
Есть три способа получить множество, и во всех трёх сразу видно главное свойство — дубликаты исчезают сами.
fruits = {"яблоко", "банан", "яблоко", "вишня", "банан"}
print(fruits)
numbers = set([1, 2, 2, 3, 3, 3, 4])
print(numbers)
letters = set("привет")
print(letters)
fruits = {"яблоко", "банан", "яблоко", "вишня", "банан"}— перечислили пять слов через запятую в фигурных скобках, но два слова повторяются.print(fruits)— выведет{'яблоко', 'банан', 'вишня'}: три уникальных значения, повторы исчезли сами. Обратите внимание и на то, что порядок вывода может не совпадать с порядком записи.numbers = set([1, 2, 2, 3, 3, 3, 4])— функцияset()принимает список и строит из его элементов множество. Это самый частый способ превратить готовый список в множество.print(numbers)— выведет{1, 2, 3, 4}: числа без повторов.letters = set("привет")—set()умеет принимать и строку. Строка — это последовательность символов, и каждый символ становится отдельным элементом множества.print(letters)— выведет множество из уникальных букв слова «привет», например{'п', 'р', 'и', 'в', 'е', 'т'}(все буквы здесь разные, поэтому ни одна не потерялась).
list(set(мой_список)) одной строкой превращает список в множество и обратно в список, по пути избавившись от повторов. Единственная плата за это — порядок элементов может перемешаться.Почему у множества нет индексов
У списка есть пронумерованные места: lst[0] — первый элемент, lst[1] — второй. У множества такой нумерации нет в принципе, ведь порядок элементов не гарантирован и может отличаться от того, в котором вы их перечисляли.
s = {5, 2, 8, 1}
print(s)
for item in s:
print(item, end=" ")
s = {5, 2, 8, 1}— создали множество из четырёх чисел именно в этом порядке записи.print(s)— выведет, например,{1, 2, 5, 8}: порядок необязательно совпадёт с тем, в каком числа перечислялись при создании.for item in s:— перебор множества цикломforработает, как и для любой другой коллекции: каждая буква строки, элемент списка или, как здесь, элемент множества по очереди попадает вitem.print(item, end=" ")— печатает каждый элемент через пробел вместо перевода строки, но в каком порядке они появятся — заранее не известно.
Обращение по номеру, как у списка (s[0]), для множества вызовет ошибку TypeError: 'set' object is not subscriptable — «объект нельзя обращаться по индексу».
Добавление элементов: add и update
Простыми словами: add кладёт в множество один новый элемент, а update — сразу несколько, беря их из списка, строки или другого множества.
Официально это методы множества: add() добавляет один объект, update() добавляет все элементы переданного итерируемого объекта (списка, строки, другого множества).
tags = {"python", "code"}
tags.add("beginner")
print(tags)
tags.add("python")
print(tags)
tags.update(["web", "backend"])
print(tags)
tags = {"python", "code"}— множество из двух строк.tags.add("beginner")— добавляет одну новую строку в множество. У множества, в отличие от списка, нет методаappend— для множеств он называетсяadd.print(tags)— теперь в множестве три элемента:{'python', 'code', 'beginner'}(порядок вывода может быть любым).tags.add("python")— пытаемся добавить то, что уже есть в множестве.print(tags)— множество не изменилось: три элемента, как и было. Никакой ошибки при этом не возникает — повтор просто не попадает внутрь.tags.update(["web", "backend"])— передали список из двух строк, и обе сразу добавились в множество.print(tags)— теперь пять элементов:{'python', 'code', 'beginner', 'web', 'backend'}.
Удаление элементов: remove, discard, pop
Для удаления есть три метода, и у каждого своё поведение на случай, если элемента не оказалось в множестве.
colors = {"red", "green", "blue"}
colors.remove("green")
print(colors)
colors.discard("pink")
print(colors)
item = colors.pop()
print(item)
colors.remove("green")— убирает элемент"green"из множества. Если бы такого элемента не было, метод бы бросил ошибкуKeyError.print(colors)— останутся два элемента:{'red', 'blue'}.colors.discard("pink")— пытаемся убрать элемент, которого в множестве нет. В отличие отremove, методdiscardв такой ситуации просто ничего не делает, без ошибки.print(colors)— множество не изменилось:{'red', 'blue'}.item = colors.pop()— удаляет из множества один элемент и одновременно возвращает его. Так как порядок не гарантирован, заранее не известно, какой именно элемент достанется —"red"или"blue".print(item)— выведет тот элемент, которыйpop()вернул и одновременно убрал из множества.
Когда что использовать:
remove() — когда уверены, что элемент есть, и хотите узнать об ошибке, если это не так.
discard() — когда не уверены, есть ли элемент: безопаснее, программа не упадёт.
pop() — когда нужен просто какой-нибудь элемент множества, неважно какой именно.
Операции с множествами: объединение, пересечение, разность
Простыми словами: представьте двух друзей и списки фильмов, которые каждый из них посмотрел. С такими списками естественно спросить: что они смотрели вместе? Что смотрел только один из них? Вот ради таких вопросов и существуют операции над множествами — в Python они устроены буквально так же, как в школьной теории множеств.
alice = {"Матрица", "Дюна", "Начало"}
bob = {"Дюна", "Начало", "Аватар"}
Две переменные-множества: alice хранит три названия фильмов, bob — тоже три, причём «Дюна» и «Начало» встречаются у обоих.
Объединение | — «всё, что смотрел хотя бы один из двух»
all_films = alice | bob
print(all_films)
Символ | между двумя множествами — «объединение». Он собирает все элементы обоих множеств в одно, без повторов. Результат: {'Матрица', 'Дюна', 'Начало', 'Аватар'}.
Пересечение & — «только то, что смотрели оба»
common = alice & bob
print(common)
Символ & оставляет только те элементы, которые есть в обоих множествах сразу. Результат: {'Дюна', 'Начало'}.
Разность - — «у Алисы есть, у Боба нет»
only_alice = alice - bob
print(only_alice)
alice - bob оставляет только те элементы alice, которых нет в bob. Результат: {'Матрица'}. Разность несимметрична: bob - alice дало бы другой ответ — {'Аватар'}.
Симметричная разность ^ — «есть у одного, но не у обоих сразу»
unique_to_each = alice ^ bob
print(unique_to_each)
^ оставляет всё, что встречается ровно в одном из двух множеств, и убирает то, что общее. Результат: {'Матрица', 'Аватар'} — это объединение alice | bob, из которого убрали общую часть alice & bob.
Подмножества: issubset и in
Простыми словами: иногда нужно узнать не «что общего», а «полностью ли одно множество умещается внутри другого». Например, все ли обязательные темы курса уже пройдены студентом.
За это отвечает метод issubset() — «является подмножеством». Есть и обратный метод, issuperset() — «является надмножеством», то есть содержит ли множество все элементы другого.
beginners = {"python", "variables", "loops"}
full_course = {"python", "variables", "loops", "sets", "files"}
print(beginners.issubset(full_course))
print(full_course.issubset(beginners))
print("python" in full_course)
beginners.issubset(full_course)— спрашиваем: все ли элементыbeginnersесть внутриfull_course? Да, все три темы там есть, поэтому будетTrue.full_course.issubset(beginners)— а теперь наоборот: весь лиfull_courseумещается вbeginners? Нет, вfull_courseесть темы, которых вbeginnersнет, поэтомуFalse."python" in full_course— проверка принадлежности черезinработает у множества так же, как у строки или списка:True, если элемент есть, иначеFalse. Здесь будетTrue.
x in множество выполняется практически мгновенно, независимо от того, сколько в нём элементов. У списка та же проверка требует по очереди просмотреть все элементы. Если элементов много и принадлежность нужно проверять часто — множество быстрее.frozenset: множество, которое нельзя изменить
Простыми словами: обычное множество можно менять после создания — добавлять и убирать элементы. А бывают ситуации, где такую свободу нужно нарочно забрать, чтобы никто (в том числе вы сами по ошибке) не поменял набор элементов позже.
Для этого есть frozenset — «замороженное множество». Оно ведёт себя как обычное множество, но после создания его нельзя изменить: ни добавить элемент, ни убрать.
fs = frozenset(["read", "write"])
print(fs)
fs2 = frozenset(["write", "delete"])
print(fs & fs2)
fs = frozenset(["read", "write"])— функцияfrozenset()принимает список (или любую другую коллекцию) и строит из него неизменяемое множество.print(fs)— выведетfrozenset({'read', 'write'}): Python явно показывает, что это именно frozenset, а не обычное множество.fs2 = frozenset(["write", "delete"])— ещё один frozenset.fs & fs2— операции с множествами (&,|,-,^) у frozenset работают точно так же, как у обычного: они не меняют исходный frozenset, а создают новый.
Попытка изменить frozenset — например, fs.add("delete") — вызовет ошибку AttributeError, потому что у frozenset такого метода попросту нет: неизменяемость гарантирована на уровне самого типа.
set использовать нельзя — например, как ключ словаря. У обычного множества такого права нет именно потому, что оно изменяемое.Практика: убрать дубликаты и найти совпадения
Соберём вместе то, что уже разобрали, на двух частых практических задачах.
Убрать повторы из списка одной строкой
votes = ["Алиса", "Боб", "Алиса", "Вера", "Боб"]
unique_voters = list(set(votes))
print(unique_voters)
print(f"Уникальных голосующих: {len(unique_voters)}")
votes = [...]— список с именами, некоторые повторяются.list(set(votes))— сначалаset(votes)строит множество из списка (повторы пропадают), затемlist(...)оборачивает результат обратно в список, ведь дальше со списком удобнее работать — например, обращаться по индексу.print(unique_voters)— выведет список из четырёх уникальных имён, но в произвольном порядке:['Боб', 'Вера', 'Алиса'](порядок мог быть и другим).print(f"Уникальных голосующих: {len(unique_voters)}")—len()считает длину списка, а f-строка подставляет число прямо в текст. ВыведетУникальных голосующих: 3.
Найти общее и разное между двумя наборами
required_list = ["python", "sql", "git", "docker"]
my_list = ["python", "git", "linux"]
required = set(required_list)
have = set(my_list)
match = required & have
missing = required - have
print(f"Совпадают: {match}")
print(f"Не хватает: {missing}")
required = set(required_list),have = set(my_list)— превращаем оба списка в множества, чтобы можно было применить операции пересечения и разности.match = required & have— пересечение: то, что нужно и уже есть. Получится{'python', 'git'}.missing = required - have— разность: то, что нужно, но ещё не освоено. Получится{'sql', 'docker'}.- Обе f-строки подставляют получившиеся множества прямо в текст вывода.
Частые ошибки
Ошибка 1. Пустое множество через {}
seen = {}
seen.add("a")
Здесь будет AttributeError: 'dict' object has no attribute 'add', потому что {} создал словарь, а у словаря нет метода add. Правильно: seen = set().
Ошибка 2. Обращение по индексу
colors = {"red", "green"}
first = colors[0]
TypeError: 'set' object is not subscriptable — у множества нет пронумерованных мест. Если нужен порядок и доступ по номеру — используйте список.
Ошибка 3. remove() на несуществующем элементе
colors = {"red", "green"}
colors.remove("blue")
KeyError: 'blue' — элемента "blue" в множестве не было, а remove() в такой ситуации бросает ошибку. Если не уверены, что элемент точно есть — используйте discard(), он не упадёт.
Что важно запомнить
{}— это словарь, не множество. Пустое множество: толькоset()- Множество хранит только уникальные элементы — дубли исчезают автоматически
- У множества нет индексов и нет гарантий порядка
add()добавляет один элемент,update()— сразу несколькоremove()бросаетKeyErrorна отсутствующем элементе,discard()молчит- Операции:
|объединение,&пересечение,-разность,^симметричная разность issubset()проверяет, что все элементы одного множества есть в другомfrozenset— неизменяемая версия множестваlist(set(lst))— быстрый способ убрать дубликаты из списка (без сохранения порядка)- Проверка
x in множествовыполняется практически мгновенно, в отличие от списка
Проверьте себя
5 вопросов
Операции с множествами
PremiumУникальные элементы
PremiumЕсть ли повторы в строке
Дана строка word. Определите, есть ли в ней повторяющиеся буквы (какая-то буква встречается больше одного раза).
Результат запишите в переменную has_duplicates: True, если повтор есть, False, если все буквы в слове разные.
Пример: для строки "привет" все буквы разные — has_duplicates должен быть False. Для строки "яблоко" буква «о» встречается дважды — has_duplicates должен быть True.