Sets
A set is an unordered collection of unique values. Sets automatically ignore duplicates, and they're great for questions like "Which students are in both classes?" or "Have we seen this value before?"
Creating a set
Write the values inside curly braces, or convert another collection with set():
colors = {"red", "green", "blue", "red", "green"}
print(colors) # no duplicates
print(len(colors))
numbers = set([1, 2, 2, 3, 3, 3])
print(numbers)Sets have no order. The items may print in a different order from the one you typed, and the order can even change between runs. For the same reason you can't use indexes like colors[0]; that's a TypeError. Use sorted() when you need a predictable order.
set() works on any collection. Given a string, it gives you the unique characters: set("hello") is {'h', 'e', 'l', 'o'}, in some order.
Watch out:
{}creates an empty dictionary, not a set. Useset()for an empty set.
print(type({}))
print(type(set()))Removing duplicates
The most common use of a set is removing duplicates from a list:
visitors = ["ana", "ben", "ana", "cleo", "ben", "ana"] unique = set(visitors) print(unique) print(len(unique), "unique visitors") # back to an ordered list print(sorted(unique))
The set keeps one copy of each name and throws the repeats away, so len() counts unique visitors. Two things are lost on the way: how many times each name appeared, and the original order. sorted() gives you back a list in alphabetical order. (To count repeats, use a dictionary, as in the Dictionaries lesson.)
Adding and removing
tags = {"python", "code"}
tags.add("beginner")
# already there, so nothing happens
tags.add("python")
print(tags)
tags.remove("code")
# not there, but no error
tags.discard("java")
print(tags)| Method | What it does | If the item is already there / missing |
|---|---|---|
add(x) |
adds one item | already there: nothing happens |
remove(x) |
removes the item | missing: KeyError |
discard(x) |
removes the item | missing: nothing happens, no error |
Use remove() when the item should definitely be there (so a mistake shows up as an error), and discard() when you just want to make sure it's gone.
Fast membership tests
in works on lists too, but sets are built for it. To answer in for a list, Python checks the items one by one, so a list of a million items can take a million checks. A set jumps straight to where the item would be, so the check stays fast no matter how big the set gets:
banned_words = {"spam", "scam", "free money"}
print("spam" in banned_words)
print("hello" in banned_words)Set operations
Sets are great for comparing two groups. Imagine two school clubs:
chess = {"Ana", "Ben", "Cleo", "Dev"}
drama = {"Cleo", "Dev", "Eli", "Fay"}
# in either club (or both)
print(chess | drama)
# in both clubs
print(chess & drama)
# in chess but not drama
print(chess - drama)
# in drama but not chess
print(drama - chess)Cleo and Dev are in both clubs, so they appear once in chess | drama (six names in all) and are the whole answer for chess & drama. Order matters for -: chess - drama is Ana and Ben, but drama - chess is Eli and Fay. Each operation gives a new set; chess and drama don't change.
| Question | Operator | Name |
|---|---|---|
| Who is in either club? | a | b |
union |
| Who is in both clubs? | a & b |
intersection |
Who is in a but not b? |
a - b |
difference |
Going deeper: More ways to compare sets optional
^ gives the items that are in exactly one of the sets (the "symmetric difference"):
chess = {"Ana", "Ben", "Cleo", "Dev"}
drama = {"Cleo", "Dev", "Eli", "Fay"}
print(chess ^ drama)Each operator also has a method version, such as chess.union(drama) or chess.intersection(drama).
Subset checks tell you whether one set is completely inside another:
required = {"python", "git"}
skills = {"python", "git", "sql", "docker"}
# are all required skills present?
print(required.issubset(skills))
print(required <= skills)
print(skills.issuperset(required))
# nothing in common?
print(required.isdisjoint({"java"}))Going deeper: What can go in a set? optional
Like dictionary keys, set items must be immutable: numbers, strings and tuples are fine, but lists aren't:
points = {(0, 0), (1, 2), (0, 0)}
print(points)
bad = {[1, 2], [3, 4]}Going deeper: Frozen sets optional
A frozenset is a set that can't be changed, just as a tuple is a list that can't be changed. You can still test membership and compare it with other sets, but add and remove don't exist:
vowels = frozenset("aeiou")
print("e" in vowels)
print(len(vowels))
vowels.add("y")Because it can't change, a frozenset can go inside another set or be a dictionary key. That's handy for pairs where the order doesn't matter:
matches = {frozenset({"Ana", "Ben"}), frozenset({"Ben", "Ana"})}
# the same pair: stored once
print(len(matches))Exercises
Exercise 1: Unique words
Print how many different words are in the sentence. It should print 7. Hint: split the sentence, then make a set.
sentence = "the cat and the dog and the bird saw a cat"
sentence.split() gives a list of words.
Turn the list into a set to remove repeats, then use len().
Exercise 2: Common friends
Print the friends that Ana and Ben have in common, as a sorted list: ['Cleo', 'Eli'].
ana_friends = {"Cleo", "Dev", "Eli", "Gus"}
ben_friends = {"Eli", "Fay", "Cleo", "Hal"}
The & operator keeps only the items that are in both sets.
Wrap the result in sorted() to get an ordered list.
Exercise 3: Missing items
needed is what a recipe requires and have is what's in the kitchen. Print a sorted list of what you still need to buy: ['eggs', 'sugar'].
needed = {"flour", "eggs", "sugar", "butter"}
have = {"flour", "butter", "milk"}
The - operator keeps what is in the first set but not in the second.
Wrap the result in sorted().
Exercise 4: Any duplicates?
Print True if the list contains any duplicate values, otherwise False. Compare the length of the list with the length of a set made from it.
ids = [104, 221, 309, 104, 412]
A set removes duplicates. If the set is smaller than the list, something was repeated.
Compare len(ids) with len(set(ids)).
Set methods at a glance
Each example starts from s = {1, 2, 3}. The Result column shows what the method gives back, or what s looks like afterwards for methods that change it.
| Method | What it does | Example | Result |
|---|---|---|---|
add(x) |
Adds x (nothing happens if it's already there) |
s.add(4) |
{1, 2, 3, 4} |
update(items) |
Adds every item from another collection | s.update([4, 5]) |
{1, 2, 3, 4, 5} |
remove(x) |
Removes x (KeyError if missing) |
s.remove(2) |
{1, 3} |
discard(x) |
Removes x if it's there, no error if not |
s.discard(9) |
{1, 2, 3} |
pop() |
Removes and returns an arbitrary item | s.pop() |
returns one item, e.g. 1 |
clear() |
Removes every item | s.clear() |
set() |
copy() |
Returns a new copy | s.copy() |
{1, 2, 3} |
union(other) or s | other |
Items in either set | s | {3, 4} |
{1, 2, 3, 4} |
intersection(other) or s & other |
Items in both sets | s & {2, 3, 4} |
{2, 3} |
difference(other) or s - other |
Items in s but not in other |
s - {3, 4} |
{1, 2} |
symmetric_difference(other) or s ^ other |
Items in exactly one of the sets | s ^ {3, 4} |
{1, 2, 4} |
issubset(other) or s <= other |
Is every item of s also in other? |
{1, 2} <= s |
True |
issuperset(other) or s >= other |
Does s contain every item of other? |
s >= {1, 5} |
False |
isdisjoint(other) |
Do the sets have nothing in common? | s.isdisjoint({7, 8}) |
True |
len(s), x in s, sorted(s) and set(items) also work, just as they do for lists.
Summary
- A set holds unique values with no order:
{1, 2, 3}. set(some_list)removes duplicates. Useset(), not{}, for an empty set.add,removeanddiscardchange a set.inchecks membership quickly.|gives items in either set,&items in both, and-items in the first but not the second.
Know something worth teaching? Write your own tutorial with runnable examples and share it.