CMP-250 · Lesson 7 of 12 · 27 min
Sets and set operations
A set is a collection of distinct objects, and it is the most basic structure in mathematics. Almost every other structure in this course is built from sets: functions and relations are sets of pairs, graphs are sets of vertices and edges, and a database table is a set of rows. Python's set type, which you used in CMPUT-174, implements the finite case directly, so you can experiment with every idea in this lesson.
Describing sets
A set is determined entirely by its elements. Order and repetition do not matter: \(\{1, 2, 3\}\), \(\{3, 1, 2\}\) and \(\{1, 1, 2, 3\}\) are the same set. We write \(x \in A\) for x is an element of A and \(x \notin A\) for x is not an element of A. Sets can be described in two main ways. Roster notation lists the elements: \(V = \{a, e, i, o, u\}\). Set-builder notation gives a property: \(E = \{n \in \mathbb{Z} \mid n \text{ is even}\}\), read as the set of integers n such that n is even.
Some sets have standard names. \(\mathbb{N}\) is the natural numbers; in computing, and in this course, \(\mathbb{N} = \{0, 1, 2, \ldots\}\), though some texts start at 1. \(\mathbb{Z}\) is the integers, \(\mathbb{Q}\) the rational numbers and \(\mathbb{R}\) the real numbers. The empty set, written \(\varnothing\) or \(\{\}\), has no elements at all. Note that \(\{\varnothing\}\) is not empty: it is a set with one element, which happens to be the empty set. A box containing an empty box is not an empty box.
A = {3, 1, 2, 2, 1}
print("A =", A, " size:", len(A))
print("{1,2,3} == {3,2,1}:", {1, 2, 3} == {3, 2, 1})
evens = {n for n in range(-6, 7) if n % 2 == 0} # set-builder notation as a comprehension
print("evens in -6..6:", sorted(evens))
empty = set() # note: {} is an empty dict, not a set
print("len(set()) =", len(empty), " len({frozenset()}) =", len({frozenset()}))A = {1, 2, 3} size: 3
{1,2,3} == {3,2,1}: True
evens in -6..6: [-6, -4, -2, 0, 2, 4, 6]
len(set()) = 0 len({frozenset()}) = 1Subsets and equality
A set \(A\) is a subset of \(B\), written \(A \subseteq B\), if every element of \(A\) is also an element of \(B\): \(\forall x\, (x \in A \to x \in B)\). If in addition \(A \ne B\), then \(A\) is a proper subset, written \(A \subset B\). Two facts follow straight from the definition. Every set is a subset of itself. The empty set is a subset of every set, because the condition \(x \in \varnothing \to x \in B\) has a false hypothesis for every \(x\), so it is vacuously true.
Two sets are equal exactly when each is a subset of the other. This gives the standard method for proving set equality, called double inclusion: show \(A \subseteq B\) and \(B \subseteq A\). To prove \(A \subseteq B\), take an arbitrary element \(x \in A\) and show \(x \in B\).
Set operations
The basic operations correspond exactly to the logical connectives of lesson 2. Union collects elements in either set (or), intersection those in both (and), difference those in the first but not the second (and not), and the complement those not in the set, relative to a universal set \(U\) of everything under discussion.
| Operation | Notation | Definition | Python | Logic |
|---|---|---|---|---|
| Union | A ∪ B | {x | x ∈ A or x ∈ B} | A | B | ∨ |
| Intersection | A ∩ B | {x | x ∈ A and x ∈ B} | A & B | ∧ |
| Difference | A − B | {x | x ∈ A and x ∉ B} | A - B | ∧ ¬ |
| Symmetric difference | A ⊕ B | {x | x in exactly one of A, B} | A ^ B | ⊕ |
| Complement | Ā | {x ∈ U | x ∉ A} | U - A | ¬ |
U = set(range(1, 11))
A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7}
print("A | B =", sorted(A | B))
print("A & B =", sorted(A & B))
print("A - B =", sorted(A - B))
print("A ^ B =", sorted(A ^ B))
print("comp A =", sorted(U - A))
print("A & B <= A:", (A & B) <= A, " A <= A | B:", A <= A | B)A | B = [1, 2, 3, 4, 5, 6, 7] A & B = [4, 5] A - B = [1, 2, 3] A ^ B = [1, 2, 3, 6, 7] comp A = [6, 7, 8, 9, 10] A & B <= A: True A <= A | B: True
Sets \(A\) and \(B\) are disjoint if \(A \cap B = \varnothing\). A collection of non-empty, pairwise disjoint sets whose union is a set \(S\) is a partition of \(S\). Partitions are everywhere in computing: a hash table partitions its keys into buckets, and in lesson 9 you will see that equivalence relations correspond exactly to partitions.
Set identities
Because each set operation corresponds to a connective, every equivalence law of lesson 3 gives a set identity. De Morgan's laws become \(\overline{A \cup B} = \bar{A} \cap \bar{B}\) and \(\overline{A \cap B} = \bar{A} \cup \bar{B}\). Distributivity becomes \(A \cap (B \cup C) = (A \cap B) \cup (A \cap C)\). Each can be proved by double inclusion, by a membership table (the set version of a truth table) or by reducing to the logical law.
Worked problem
Proving a set identity by double inclusion
Prove: A − (B ∪ C) = (A − B) ∩ (A − C).
Show solution
- (⊆) Let x ∈ A − (B ∪ C). Then x ∈ A and x ∉ B ∪ C, so x ∉ B and x ∉ C. Hence x ∈ A − B and x ∈ A − C, so x is in the intersection.
- (⊇) Let x ∈ (A − B) ∩ (A − C). Then x ∈ A, x ∉ B and x ∉ C. So x ∉ B ∪ C, and x ∈ A − (B ∪ C).
- Each side is a subset of the other, so they are equal.
Answer: Proved. In logic the identity is a ∧ ¬(b ∨ c) ≡ (a ∧ ¬b) ∧ (a ∧ ¬c), which follows from De Morgan.
from itertools import product
# Membership table: each row says whether an element is in A, B, C.
ok = True
for a, b, c in product([True, False], repeat=3):
lhs = a and not (b or c) # x in A - (B | C)
rhs = (a and not b) and (a and not c) # x in (A - B) & (A - C)
ok &= (lhs == rhs)
print("membership table proves A-(B|C) == (A-B)&(A-C):", ok)
import random
random.seed(7)
U = range(20)
for _ in range(3):
A, B, C = ({x for x in U if random.random() < 0.5} for _ in range(3))
print(A - (B | C) == (A - B) & (A - C), end=" ")
print()membership table proves A-(B|C) == (A-B)&(A-C): True True True True
Power sets
The power set of \(A\), written \(\mathcal{P}(A)\), is the set of all subsets of \(A\). For \(A = \{a, b, c\}\), the power set has 8 elements: \(\varnothing\), \(\{a\}\), \(\{b\}\), \(\{c\}\), \(\{a, b\}\), \(\{a, c\}\), \(\{b, c\}\) and \(\{a, b, c\}\). In general, if \(|A| = n\) then \(|\mathcal{P}(A)| = 2^n\), because building a subset means deciding, for each of the \(n\) elements independently, whether to include it.
That argument links power sets to binary numbers: a subset of an \(n\)-element set corresponds to an \(n\)-bit string, with bit \(i\) equal to 1 if element \(i\) is included. The integers 0 to \(2^n - 1\) therefore enumerate all subsets, which is a standard programming technique.
items = ["a", "b", "c"]
n = len(items)
for mask in range(2 ** n):
subset = [items[i] for i in range(n) if mask >> i & 1]
print(f"{mask:03b} -> {{{', '.join(subset)}}}")
print("number of subsets:", 2 ** n)000 -> {}
001 -> {a}
010 -> {b}
011 -> {a, b}
100 -> {c}
101 -> {a, c}
110 -> {b, c}
111 -> {a, b, c}
number of subsets: 8Exponential growth makes power sets enormous. A set of 30 elements has over a billion subsets, and 300 elements give more subsets than there are atoms in the observable universe. That is why algorithms that try every subset, like the brute-force SAT solver in lesson 3, quickly become impractical, and why algorithm design (CMPUT-204 and CMPUT-304) looks for cleverer approaches.
Cartesian products and tuples
An ordered pair \((a, b)\) differs from the set \(\{a, b\}\): order matters, so \((1, 2) \ne (2, 1)\), and repetition is allowed, as in \((3, 3)\). The Cartesian product \(A \times B\) is the set of all ordered pairs with first element from \(A\) and second from \(B\). If \(|A| = m\) and \(|B| = n\), then \(|A \times B| = mn\). More generally, \(A_1 \times \cdots \times A_k\) contains the \(k\)-tuples, and \(A^k\) means \(A \times \cdots \times A\) with \(k\) factors.
Cartesian products are the mathematics behind many data structures. A Python tuple is an element of a Cartesian product. A row in a database table with columns name, age and province is an element of \(\text{Names} \times \text{Ages} \times \text{Provinces}\). Every pixel position on a 1920 by 1080 screen is an element of \(\{0, \ldots, 1919\} \times \{0, \ldots, 1079\}\), a set with 2,073,600 elements.
from itertools import product
sizes = ["S", "M", "L"]
finishes = ["matte", "gloss"]
variants = list(product(sizes, finishes))
print(variants)
print("|sizes x finishes| =", len(variants), "= 3 * 2")
print("|{0,1}^8| =", len(list(product([0, 1], repeat=8))), "(the number of bytes)")[('S', 'matte'), ('S', 'gloss'), ('M', 'matte'), ('M', 'gloss'), ('L', 'matte'), ('L', 'gloss')]
|sizes x finishes| = 6 = 3 * 2
|{0,1}^8| = 256 (the number of bytes)Inclusion–exclusion
How many elements are in \(A \cup B\)? Adding \(|A| + |B|\) counts the elements of \(A \cap B\) twice, so we subtract them once. For three sets we add the three sizes, subtract the three pairwise intersections, and then add back the triple intersection, which was subtracted once too often.
Worked problem
Counting with inclusion–exclusion
How many integers from 1 to 1000 are divisible by 3, 5 or 7?
Show solution
- Let A, B, C be the multiples of 3, 5 and 7. |A| = ⌊1000/3⌋ = 333, |B| = 200, |C| = 142.
- Pairwise: multiples of 15: 66; of 21: 47; of 35: 28.
- Triple: multiples of 105: 9.
- Total = 333 + 200 + 142 − 66 − 47 − 28 + 9 = 543.
Answer: 543 integers. The program below checks this by direct counting.
N = 1000
direct = sum(1 for n in range(1, N + 1) if n % 3 == 0 or n % 5 == 0 or n % 7 == 0)
f = lambda d: N // d
formula = f(3) + f(5) + f(7) - f(15) - f(21) - f(35) + f(105)
print("direct count:", direct, " inclusion-exclusion:", formula)direct count: 543 inclusion-exclusion: 543
A caution: Russell's paradox
It seems natural to allow any property to define a set. In 1901 Bertrand Russell showed that this leads to contradiction. Consider \(R = \{x \mid x \notin x\}\), the set of all sets that are not members of themselves. Is \(R \in R\)? If it is, then by definition it is not; if it is not, then by definition it is. Modern set theory avoids the paradox by restricting how sets may be formed, for example by always selecting elements from an existing set: \(\{x \in A \mid P(x)\}\). In practice, if you always specify the domain, as this course recommends, you never meet the problem. The same self-reference trick reappears in CMPUT-474 as the heart of Turing's proof that some problems cannot be solved by any program.
Common mistakes
MistakeConfusing ∈ with ⊆: writing 1 ⊆ {1, 2}.
Instead1 ∈ {1, 2} (an element), while {1} ⊆ {1, 2} (a subset).
MistakeThinking {∅} is empty.
Instead{∅} has exactly one element, the empty set.
MistakeForgetting that order and repetition do not matter in sets.
Instead{1, 2, 2} = {2, 1}; use tuples or lists when order or repetition matters.
MistakeWriting {} for an empty set in Python.
Instead{} is an empty dictionary; use set().
MistakeAdding |A| + |B| for |A ∪ B|.
InsteadSubtract |A ∩ B|, which was counted twice.
MistakeSaying |P(A)| = 2n.
Instead|P(A)| = 2 to the n.
Summary and key terms
- A set is a collection of distinct objects; order and repetition do not matter.
- A ⊆ B means every element of A is in B; ∅ is a subset of every set.
- Prove A = B by double inclusion: A ⊆ B and B ⊆ A.
- Union, intersection, difference and complement mirror ∨, ∧, ∧¬ and ¬; De Morgan's laws carry over.
- The power set of an n-element set has 2 to the n elements, enumerable with n-bit masks.
- A × B is the set of ordered pairs; |A × B| = |A||B|.
- Inclusion–exclusion corrects for double counting in unions.
- Set
- A collection of distinct objects, determined by its elements.
- Element
- An object belonging to a set, written x ∈ A.
- Subset
- A ⊆ B when every element of A is in B.
- Empty set
- The set with no elements, written ∅.
- Power set
- The set of all subsets of a set, written P(A).
- Cartesian product
- A × B, the set of ordered pairs (a, b) with a ∈ A and b ∈ B.
- Disjoint
- Having no elements in common.
- Partition
- A collection of non-empty, pairwise disjoint sets whose union is the whole set.
- Complement
- The elements of the universal set not in A.
- Inclusion–exclusion
- A counting rule that adds and subtracts intersection sizes to count a union.
Practice problems
Work each problem on paper first, then reveal the answer to check.
List the elements of {x ∈ ℤ | x² < 10}.
Show answer
{−3, −2, −1, 0, 1, 2, 3}.
Is ∅ ∈ {1, 2}? Is ∅ ⊆ {1, 2}?
Show answer
No; yes.
Let A = {1, 2, 3}, B = {2, 3, 4}. Find A ∩ B, A ∪ B, A − B, B − A.
Show answer
{2, 3}; {1, 2, 3, 4}; {1}; {4}.
How many elements does P({1, 2, 3, 4, 5}) have?
Show answer
2 to the 5 = 32.
Prove A ∩ B ⊆ A.
Show answer
If x ∈ A ∩ B then x ∈ A and x ∈ B, so in particular x ∈ A.
What is |{a, b} × {1, 2, 3}|?
Show answer
2 × 3 = 6.
Use De Morgan to write the complement of A ∩ B.
Show answer
Ā ∪ B̄.
In a class of 40, 25 take Python, 18 take C and 10 take both. How many take neither?
Show answer
40 − (25 + 18 − 10) = 7.
Write a Python expression for the set of squares of 1 to 10 that are odd.
Show answer
{n * n for n in range(1, 11) if n * n % 2 == 1}
Explain why |P(A)| = 2 to the n using binary strings.
Show answer
Each subset corresponds to an n-bit string recording membership of each element; there are 2 to the n such strings.
Lesson quiz · 5 questions
Score 60% or more to complete this lesson.
Create a free account to save your progress, take quizzes and earn a certificate.