Python Sets: Unique Unordered Collections

A set stores unique values with no order and answers membership questions almost instantly. That trade - lose order, gain speed and uniqueness - is the whole point.

Creating a set

numbers = {1, 2, 3}
names = {"Meera", "Arun"}

empty = set()               # NOT {} - that is an empty dictionary
print(type({}))             # <class 'dict'>
print(type(set()))          # <class 'set'>

from_list = set([1, 2, 2, 3, 3, 3])
print(from_list)            # {1, 2, 3} - duplicates removed

from_string = set("mississippi")
print(from_string)          # {'m', 'i', 's', 'p'} in some order

The three defining properties

  • Unique. Adding a value that is already present does nothing.
  • Unordered. There are no positions, so no indexing and no slicing.
  • Fast membership. x in s is roughly constant time regardless of size.
s = {3, 1, 2}

print(len(s))          # 3
print(2 in s)          # True
# print(s[0])          # TypeError: 'set' object is not subscriptable
s.add(3)
print(len(s))          # 3 - unchanged, 3 was already there
A set printing in sorted order for small integers is a coincidence of how they hash. Never rely on it. If order matters, use a list, or call sorted() when you display.

Only hashable values

valid = {1, "two", (3, 4), True, None, 3.5}
# invalid = {[1, 2]}          # TypeError: unhashable type: 'list'
# invalid = {{"a": 1}}        # TypeError: unhashable type: 'dict'

pairs = {(0, 0), (1, 2)}      # tuples are fine
print((1, 2) in pairs)        # True

Set membership is implemented with hashing, and a value that could change would break the lookup. That is why only immutable values are allowed.

An equality subtlety

print({1, True})          # {1} - True equals 1, so one of them is dropped
print({0, False, ""})     # {0, ''} - 0 and False collide, "" does not

Adding and removing

s = {1, 2}

s.add(3)                  # add ONE item
print(s)                  # {1, 2, 3}

s.update([4, 5])          # add every item of an iterable
s.update("ab", {6})       # several iterables at once
print(s)                  # {1, 2, 3, 4, 5, 6, 'a', 'b'}

s.remove(1)               # KeyError if not present
s.discard(99)             # no error if not present
print(s)

value = s.pop()           # removes and returns an ARBITRARY item
print(value)

s.clear()
print(s)                  # set()
Missing value
remove(x)Raises KeyError
discard(x)Does nothing

Why membership is fast

import time

big_list = list(range(1_000_000))
big_set = set(big_list)

start = time.perf_counter()
print(999_999 in big_list)
print("list:", time.perf_counter() - start)

start = time.perf_counter()
print(999_999 in big_set)
print("set: ", time.perf_counter() - start)

The list must compare against up to a million items. The set computes one hash and looks in one place. For a single check the difference is invisible; inside a loop that runs a million times it is the difference between minutes and milliseconds.

blocked = ["user1", "user2", "user3"]     # if this grows large...
requests = ["user7", "user2", "user9"]

blocked = set(blocked)                     # ...convert ONCE, before the loop
allowed = [r for r in requests if r not in blocked]
print(allowed)

frozenset

immutable = frozenset([1, 2, 3])
print(2 in immutable)          # True
# immutable.add(4)             # AttributeError

# A frozenset IS hashable, so it can be a key or live inside a set
groups = {frozenset({"a", "b"}), frozenset({"c"})}
print(len(groups))             # 2

lookup = {frozenset({1, 2}): "pair one-two"}
print(lookup[frozenset({2, 1})])     # order does not matter

Iterating and converting

s = {3, 1, 2}

for value in s:                # order is not guaranteed
    print(value, end=" ")
print()

for value in sorted(s):        # deterministic
    print(value, end=" ")
print()

print(list(s))
print(sum(s), min(s), max(s), len(s))

Set comprehensions

words = ["apple", "banana", "avocado", "cherry"]

initials = {w[0] for w in words}
print(initials)                        # {'a', 'b', 'c'}

squares = {n * n for n in range(-3, 4)}
print(squares)                         # {0, 1, 4, 9} - duplicates collapse

Common mistakes

  • Writing {} for an empty set. That is a dictionary.
  • Trying to index or slice a set.
  • Putting a list inside a set.
  • Relying on the order a set prints in.
  • Using remove where discard was safer.
  • Converting to a set to deduplicate when the original order mattered.

Best practices

  • Use a set when you need uniqueness or repeated membership testing.
  • Convert a lookup list to a set once, outside the loop that uses it.
  • Use discard when absence is acceptable.
  • Use sorted(s) whenever a set is displayed.
  • Use dict.fromkeys instead of a set when deduplicating must preserve order.

Practice

  1. Count the distinct characters in a sentence, ignoring case and spaces.
  2. Explain why {1, True, 1.0} has one element.
  3. Deduplicate a list twice: once with a set and once preserving order, and compare the results.
  4. Store a collection of coordinate pairs in a set and test membership.
  5. Explain when a frozenset is required rather than a set.

Conclusion

A set gives up order and indexing in exchange for guaranteed uniqueness and near instant membership tests. Reach for one whenever you find yourself asking "have I seen this before?" inside a loop.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Python notes →
Python

Sorting Algorithms

Python sorts for you in n log n. Implementing bubble, insertion, merge and quick sort is still worth doing, because it teaches how algorithms are comp...

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.