Skip to content

Sets

Many problems come down to a bag of things with no order and no repeats. The distinct words in a book, the student numbers enrolled in a course, the users who have followed one account on a social network, and the IP addresses that visited a web page in the last hour are all collections of this kind: the order they are listed in carries no meaning, and listing the same item twice adds nothing. The data structure for such a collection is the set.

Sets are a basic language of mathematics as well, and the notation introduced here recurs in logic, probability, and discrete mathematics. These notes cover the set, its operations and notation in math, and the set type that Python provides, which supports all of the same operations and runs them quickly.

A set is an unordered collection of distinct elements. Two sets are equal when they contain exactly the same elements; the order in which they are listed and any apparent repetition do not matter. For instance, {1,2,3}\{1, 2, 3\}, {3,2,1}\{3, 2, 1\}, and {1,2,2,3,3,3}\{1, 2, 2, 3, 3, 3\} all name the same set.

An element may belong to a set or not, and nothing else is said about its position. Compare this with a list, in which each element has a specific index, or with a priority queue, in which each element has a priority.

A set may have any number of elements, including none. The empty set, written ∅\emptyset or { }\{\,\}, has no elements. The elements themselves may be numbers, strings, or whole other sets; what matters is only that each element either is or is not in the set.

Three symbols cover most of what one writes about a single set:

  • x∈Sx \in S means that xx belongs to the set SS. One reads it ”xx is in SS” or ”xx is an element of SS”. Its negation, x∉Sx \notin S, means that xx is not an element of SS.
  • ∣S∣|S| is the number of elements in SS, called the cardinality of SS. The empty set has ∣∅∣=0|\emptyset| = 0.
  • Set literals are written with braces, as {2,3,5,7,11}\{2, 3, 5, 7, 11\}.

A few sets are so common that they have reserved names: N\mathbb{N} for the natural numbers 0,1,2,…0, 1, 2, \ldots, Z\mathbb{Z} for the integers, Q\mathbb{Q} for the rationals, and R\mathbb{R} for the real numbers.

Listing every element is only practical when the set is small. For larger sets, set-builder notation describes the set by a rule its elements must satisfy. Two forms recur:

  • {x∈S:P(x)}\{x \in S : P(x)\} is the set of elements of SS for which the condition P(x)P(x) holds. Read it “the set of xx in SS such that PP of xx”. Some books write a vertical bar in place of the colon, as {x∈S∣P(x)}\{x \in S \mid P(x)\}; the meaning is the same.
  • {f(x):x∈S}\{f(x) : x \in S\} is the set of values that ff takes on SS.

For example, {n∈N:n<10 and n is even}={0,2,4,6,8}\{n \in \mathbb{N} : n < 10 \text{ and } n \text{ is even}\} = \{0, 2, 4, 6, 8\}, and {n2:n∈{1,2,3,4}}={1,4,9,16}\{n^2 : n \in \{1, 2, 3, 4\}\} = \{1, 4, 9, 16\}.

A set AA is a subset of a set BB, written A⊆BA \subseteq B, when every element of AA is also an element of BB. Equivalently, x∈Ax \in A implies x∈Bx \in B for every xx. The empty set is a subset of every set, and every set is a subset of itself.

Two sets are equal exactly when each is a subset of the other: A=BA = B iff A⊆BA \subseteq B and B⊆AB \subseteq A. This two-sided check is the usual way to prove that two sets are equal in the general case, when they are not given by listing their elements.

If A⊆BA \subseteq B and A≠BA \neq B, then AA is a proper subset of BB, written A⊂BA \subset B. The symbols ⊇\supseteq and ⊃\supset reverse the direction: B⊇AB \supseteq A means the same as A⊆BA \subseteq B.

Three operations build new sets from old ones:

  • Union A∪B={x:x∈A or x∈B}A \cup B = \{x : x \in A \text{ or } x \in B\}, the elements in at least one of AA or BB.
  • Intersection A∩B={x:x∈A and x∈B}A \cap B = \{x : x \in A \text{ and } x \in B\}, the elements in both.
  • Difference A∖B={x:x∈A and x∉B}A \setminus B = \{x : x \in A \text{ and } x \notin B\}, the elements in AA but not in BB. Some books write this as A−BA - B.

Union and intersection are commutative and associative, so A∪B=B∪AA \cup B = B \cup A, and (A∪B)∪C=A∪(B∪C)(A \cup B) \cup C = A \cup (B \cup C), and the same for ∩\cap. Difference is neither: in general A∖B≠B∖AA \setminus B \neq B \setminus A. Two sets AA and BB are called disjoint when A∩B=∅A \cap B = \emptyset.

A Venn diagram pictures these operations as overlapping regions. The union is the whole shaded area under either circle; the intersection is the lens where the circles overlap; the difference A∖BA \setminus B is the part of AA‘s circle outside BB‘s.

A B A ∩ B

Counting the elements of a union must avoid counting the overlap twice. For two sets,

∣A∪B∣=∣A∣+∣B∣−∣A∩B∣.|A \cup B| = |A| + |B| - |A \cap B|.

For three sets, each pairwise intersection is subtracted, and the triple intersection is added back:

∣A∪B∪C∣=∣A∣+∣B∣+∣C∣−∣A∩B∣−∣A∩C∣−∣B∩C∣+∣A∩B∩C∣.|A \cup B \cup C| = |A| + |B| + |C| - |A \cap B| - |A \cap C| - |B \cap C| + |A \cap B \cap C|.

This pattern of alternating signs is the inclusion–exclusion principle, which generalizes to any number of sets.

Two further constructions appear often. The Cartesian product of AA and BB is the set of ordered pairs with first entry from AA and second from BB:

A×B={(a,b):a∈A and b∈B}.A \times B = \{(a, b) : a \in A \text{ and } b \in B\}.

Its cardinality is ∣A×B∣=∣A∣⋅∣B∣|A \times B| = |A| \cdot |B|. The nn-fold product An=A×A×⋯×AA^n = A \times A \times \cdots \times A holds all tuples of length nn with entries from AA.

The power set of SS, written P(S)\mathcal{P}(S) or 2S2^S, is the set of all subsets of SS:

P(S)={T:T⊆S}.\mathcal{P}(S) = \{T : T \subseteq S\}.

A finite set with nn elements has ∣P(S)∣=2n|\mathcal{P}(S)| = 2^n subsets: for each element, a subset either includes it or does not, and the choices for different elements are independent. For example, P({a,b})={∅,{a},{b},{a,b}}\mathcal{P}(\{a, b\}) = \{\emptyset, \{a\}, \{b\}, \{a, b\}\}, with four elements, and 22=42^2 = 4.

Python’s built-in set type carries all of the operations above. A set literal lists its elements in braces, and the set constructor turns any iterable into a set, dropping duplicates:

primes = {2, 3, 5, 7, 11}
letters = set("mississippi") # {'m', 'i', 's', 'p'}
empty = set() # not {} — that is an empty dict

The one surprise is that {} is an empty dict, not an empty set; use set() for the empty set.

Membership and cardinality mirror the mathematical notation:

3 in primes # True
4 in primes # False
len(primes) # 5

The three set operations are available both as operators and as named methods:

MathOperatorMethod
A∪BA \cup B`ab`
A∩BA \cap Ba & ba.intersection(b)
A∖BA \setminus Ba - ba.difference(b)
A△BA \mathbin{\triangle} Ba ^ ba.symmetric_difference(b)
A⊆BA \subseteq Ba <= ba.issubset(b)
A⊂BA \subset Ba < b—

The last row is the symmetric difference A△B=(A∖B)∪(B∖A)A \triangle B = (A \setminus B) \cup (B \setminus A), the elements in one of AA or BB but not both.

Sets are mutable, with methods to add and remove elements:

s = {1, 2, 3}
s.add(4) # s is now {1, 2, 3, 4}
s.discard(2) # s is now {1, 3, 4}; discard of a missing element does nothing
s.remove(5) # KeyError: 5 is not in s

Mutating variants of the binary operators update a set in place: a |= b, a &= b, a -= b, and a ^= b.

Not every value may be an element of a set. Python requires that each element be hashable: that it have a stable value under hashing that will not change for the lifetime of the object. Numbers, strings, and tuples of hashable things are all fine. Lists and other sets are not, because their contents can change after they are stored. A tuple of coordinates may belong to a set, but a list of them may not:

{(0, 0), (1, 2), (3, 4)} # a set of three points
{[0, 0], [1, 2]} # TypeError: unhashable type: 'list'

To hold a set of sets, use Python’s frozenset, an immutable version of set that is hashable.

Set-builder notation translates almost word for word into a set comprehension:

# {n in range(10) : n is even}
evens = {n for n in range(10) if n % 2 == 0}
# {n^2 : n in {1, 2, 3, 4}}
squares = {n * n for n in {1, 2, 3, 4}}
# the set of letters that appear in a string
letters = {c for c in "hello world" if c.isalpha()}

The syntax matches a list comprehension, with braces in place of brackets. The resulting set has no duplicates and no particular order.

Both x in s and s.add(x) run in near-constant time on average, independent of how many items s holds. That is why a set is the right data structure for membership tests on large collections, and why dedup through set(items) is a one-line solution. These speeds come from the way Python stores its sets, which is covered in the notes on hash tables.

A short application brings the pieces together. Given two short texts, how similar are they? One simple measure of similarity between any two sets AA and BB is the Jaccard similarity,

J(A,B)=∣A∩B∣∣A∪B∣,J(A, B) = \frac{|A \cap B|}{|A \cup B|},

which is 00 for disjoint sets and 11 for equal nonempty sets. Applied to the set of distinct words in each of two documents, Jaccard similarity gives a quick, order-independent score for how much vocabulary two texts share. It is used in search engines to spot near-duplicate web pages, and in plagiarism detectors to flag passages worth a second look.

A Python implementation is short:

def words(text):
return {word.lower() for word in text.split()}
def jaccard(a, b):
return len(a & b) / len(a | b)
doc1 = "the quick brown fox jumps over the lazy dog"
doc2 = "the quick brown dog jumps over a sleeping fox"
jaccard(words(doc1), words(doc2)) # 0.7

Seven words are shared (the, quick, brown, fox, jumps, over, dog), and the union holds ten distinct words, so J=7/10=0.7J = 7 / 10 = 0.7. The score ignores word order, which is both a strength (small rearrangements do not fool it) and a weakness (two documents built from the same vocabulary can score high even if they say opposite things).

A natural refinement is to split the text into overlapping k-grams of consecutive words, then compute the Jaccard similarity of the resulting sets of k-grams. The order of words then matters within each k-gram, and small reorderings no longer give full credit.

The treatment of set notation, equality, subsets, and the operations ∪\cup, ∩\cap, and ∖\setminus follows Rosen, Discrete Mathematics and Its Applications, chapter 2. The inclusion–exclusion principle and its proof by induction appear in the same book, chapter 8. The Jaccard similarity was introduced by Paul Jaccard in 1912 for comparing the floras of two regions.

The text, the sections on sets in Python, the document-similarity example, the figure, and the practice problems were drafted by Claude (Anthropic) at Balkcom’s direction, following the sources above, and then edited by Balkcom. The Python material can also be found in the Python documentation for set, and the use of the Jaccard similarity to compare documents in Leskovec, Rajaraman, and Ullman, Mining of Massive Datasets, chapter 3.

Try these on your own before checking the solutions.

Write out each of the following sets by listing its elements.

  1. {n∈N:n≤10 and n is a multiple of 3}\{n \in \mathbb{N} : n \leq 10 \text{ and } n \text{ is a multiple of } 3\}
  2. {n2−n:n∈{1,2,3,4,5}}\{n^2 - n : n \in \{1, 2, 3, 4, 5\}\}
  3. {(a,b)∈{1,2}×{1,2}:a≠b}\{(a, b) \in \{1, 2\} \times \{1, 2\} : a \neq b\}

(Solution)

Let A={1,2,3,4,5}A = \{1, 2, 3, 4, 5\}, B={4,5,6,7}B = \{4, 5, 6, 7\}, and C={2,4,6,8}C = \{2, 4, 6, 8\}. Compute each of the following.

  1. A∪BA \cup B
  2. A∩BA \cap B
  3. A∖BA \setminus B
  4. (A∪B)∩C(A \cup B) \cap C
  5. A∩B∩CA \cap B \cap C
  6. ∣A∪B∪C∣|A \cup B \cup C|

(Solution)

In a class of 60 students, 35 take mathematics, 28 take computer science, and 15 take both. How many take at least one of the two? How many take neither?

(Solution)

List every subset of {a,b,c}\{a, b, c\}. How many are there, and how many of them contain aa?

(Solution)

Prove that (A∪B)∖C=(A∖C)∪(B∖C)(A \cup B) \setminus C = (A \setminus C) \cup (B \setminus C) for any sets AA, BB, and CC, by showing that each side is a subset of the other.

Note: this problem is for practice only. Proof problems like this one will not be tested in CS 10.

(Solution)

Write a Python function common_letters(s, t) that returns the set of lowercase letters that appear in both of two strings s and t, ignoring case and ignoring any non-letter characters. For example, common_letters("Hello", "World!") returns {'l', 'o'}.

(Solution)

Compute the Jaccard similarity of the word sets of the two sentences “a rose is a rose” and “roses are red, roses are blue”, treating each distinct word as an element.

(Solution)