Set Operations

Difficulty: Intermediate

A set in Python is an unordered collection of unique, hashable elements. Sets are mutable (you can add and remove elements), but the elements themselves must be immutable (strings, numbers, tuples). Sets are implemented using hash tables, providing O(1) average-case time complexity for membership testing, insertion, and deletion. This makes sets ideal for tasks like deduplication, membership checks, and mathematical set operations.

You can create a set using curly braces with values {1, 2, 3}, or using the set() constructor from any iterable. Note that {} creates an empty dictionary, not an empty set; use set() for an empty set. Duplicate values are automatically removed during creation. The set() constructor is also commonly used to deduplicate lists: list(set(my_list)).

The add() method inserts a single element (no effect if it already exists). remove() deletes an element and raises KeyError if not found. discard() also deletes but silently does nothing if the element is missing, making it the safer choice. pop() removes and returns an arbitrary element (since sets are unordered, you cannot predict which one). clear() empties the entire set.

Python sets support all standard mathematical set operations. Union (a | b or a.union(b)) returns all elements from both sets. Intersection (a & b or a.intersection(b)) returns elements common to both. Difference (a - b or a.difference(b)) returns elements in a but not in b. Symmetric difference (a ^ b or a.symmetric_difference(b)) returns elements in either set but not both. These operations return new sets and do not modify the originals.

Each operation also has an in-place variant using augmented assignment operators: |=, &=, -=, ^= or the methods update(), intersection_update(), difference_update(), symmetric_difference_update(). The issubset(), issuperset(), and isdisjoint() methods test relationships between sets. These are commonly used in permission systems, tag filtering, and data analysis.

Code examples

Creating sets and basic operations

# Creating sets
fruits = {'apple', 'banana', 'cherry', 'apple'}  # duplicate removed
print('Fruits:', sorted(fruits))
print('Count:', len(fruits))

# From a list (deduplication)
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
unique = set(numbers)
print('Unique:', sorted(unique))

# Empty set (not {})
empty = set()
print('Empty set:', empty, 'Type:', type(empty))

# Set from string (unique characters)
chars = set('mississippi')
print('Unique chars:', sorted(chars))

Sets automatically remove duplicates. Note that {} creates a dict, not a set; use set() for empty sets. Sets created from strings contain unique characters. Since sets are unordered, we use sorted() for deterministic output.

add(), remove(), and discard()

colors = {'red', 'green', 'blue'}

# add() - no effect if already exists
colors.add('yellow')
colors.add('red')  # already exists, no error
print('After add:', sorted(colors))

# remove() - raises KeyError if missing
colors.remove('green')
print('After remove:', sorted(colors))

# discard() - safe, no error if missing
colors.discard('purple')  # no error
colors.discard('blue')
print('After discard:', sorted(colors))

# pop() - removes arbitrary element
popped = colors.pop()
print('Popped:', popped)
print('Remaining:', sorted(colors))

add() is idempotent. remove() raises KeyError for missing elements while discard() silently ignores them. pop() removes and returns an arbitrary element since sets have no defined order. Note: pop() output may vary across runs.

Set operations: union, intersection, difference

python_devs = {'Alice', 'Bob', 'Charlie', 'Diana'}
java_devs = {'Charlie', 'Diana', 'Eve', 'Frank'}

# Union: all developers
all_devs = python_devs | java_devs
print('All devs:', sorted(all_devs))

# Intersection: know both languages
both = python_devs & java_devs
print('Both:', sorted(both))

# Difference: Python only
python_only = python_devs - java_devs
print('Python only:', sorted(python_only))

# Symmetric difference: exactly one language
one_lang = python_devs ^ java_devs
print('One language:', sorted(one_lang))

# Method syntax (same results)
print('Union method:', sorted(python_devs.union(java_devs)))
print('Intersection method:', sorted(python_devs.intersection(java_devs)))

Operators (|, &, -, ^) and methods (union, intersection, difference, symmetric_difference) produce the same results. Operators require both operands to be sets, while methods accept any iterable as the argument.

Set relationships and frozenset

a = {1, 2, 3}
b = {1, 2, 3, 4, 5}
c = {6, 7}

print('a subset of b:', a.issubset(b))
print('b superset of a:', b.issuperset(a))
print('a disjoint from c:', a.isdisjoint(c))
print('a disjoint from b:', a.isdisjoint(b))

# frozenset: immutable set (can be used as dict key or set element)
fs = frozenset([1, 2, 3])
print('\nFrozenset:', sorted(fs))
print('Type:', type(fs))

# frozenset as dict key
cache = {frozenset(['read', 'write']): 'editor', frozenset(['read']): 'viewer'}
user_perms = frozenset(['read', 'write'])
print('Role:', cache[user_perms])

issubset(), issuperset(), and isdisjoint() test set relationships. frozenset is the immutable counterpart of set, which means it is hashable and can be used as a dictionary key or as an element of another set.

Key points

Concepts covered

set creation, add(), remove(), discard(), union, intersection, difference, symmetric_difference