Python Part I · Module 7 · Sets
Set Operations: Difference, Union, Intersection
This is what the previous three chapters were building towards. Given the failures from yesterday's run and the failures from today's, there are four questions worth asking, and every one of them is a single line of Python. The operations have mathematical names and single-character symbols, and learning them in that form is the reason so many people can recite what intersection means and still not reach for it when the question in front of them is which tests broke overnight. So this chapter starts from the questions. The symbols arrive once each question already has an answer, and one of them has a direction that it is genuinely easy to get backwards.
Foundation15 min readPythonPart IFundamentalsSetsOperations
Part I · Module 7 · Chapter 7.4 · Set Operations: Difference, Union, Intersection
1. Four Questions About Two Runs
Here is the data, and it is the same data Chapter 7.1 opened with:
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}Three failures each. Some overlap. The four questions a reviewer asks, in plain English:
- What is failing today that was not failing yesterday? — something broke.
- What was failing yesterday and is not failing today? — something got fixed.
- What is failing in both? — the persistent ones, probably the real work.
- What failed in either run? — everything that needs looking at.
Each has a one-line answer. Take them in order, and notice that the English question comes first every time — if you only remember the symbol, you will not know which one to reach for.
2. What Broke: today - yesterday
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
new_fails = today - yesterday
print(sorted(new_fails))
print(len(new_fails))
print(new_fails == {"interrupt"})['interrupt']
1
Truetoday - yesterday gives the elements that are in today and not in yesterday. One test — interrupt — is failing now and was not before. That is the new problem.
The operation is called difference, and read aloud today - yesterday is "today, minus anything that was already in yesterday".
Three ways of confirming the answer are shown there on purpose: sorted() to read it, len() to count it, and == against the set you expected. All three are reproducible, and Chapter 7.2 explains why printing new_fails directly would not be.
3. What Got Fixed: yesterday - today
Same operator. Operands swapped:
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
fixed = yesterday - today
print(sorted(fixed))
print(len(fixed))
print(fixed == {"reset"})['reset']
1
Trueyesterday - today gives what was failing yesterday and is not failing now. reset has stopped failing.
Put the two side by side, because this is the single most important thing in the chapter:
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
print(sorted(today - yesterday))
print(sorted(yesterday - today))
print((today - yesterday) == (yesterday - today))['interrupt']
['reset']
FalseBoth results have one element. Both are plausible-looking answers. They mean opposite things — one is a regression and the other is a fix — and nothing in the output would tell you which one you computed.
A useful way to remember it: - on sets reads like - on numbers. You start with the thing on the left and take away from it.
4. What Failed in Both: today & yesterday
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
both = today & yesterday
print(sorted(both))
print(len(both))
print(both == {"timeout", "crc"})['crc', 'timeout']
2
True& gives the elements present in both sets — the intersection. Two tests failed yesterday and are still failing today.
These are usually the ones that matter most in a review: not a new breakage, not a fix, but a problem that has survived a day of work.
5. What Failed in Either: today | yesterday
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
either = today | yesterday
print(sorted(either))
print(len(either))['crc', 'interrupt', 'reset', 'timeout']
4| gives everything in either set — the union. Four distinct tests were involved across the two runs.
Note the count. Three plus three is six, and the answer is four, because timeout and crc appear in both and a set holds each distinct value once. Union combines membership; it does not stack collections up.
The fourth operation: what changed either way
The module covers four operations, and this is the one the title does not name:
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
changed = today ^ yesterday
print(sorted(changed))
print(len(changed))
print(changed == {"reset", "interrupt"})['interrupt', 'reset']
2
True^ gives the elements in one set or the other but not both — the symmetric difference. In review terms: everything that changed overnight, whether it broke or got fixed, with the unchanged failures left out.
It is exactly the two differences from Sections 2 and 3 combined, which you can verify:
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
print((today ^ yesterday) == (today - yesterday) | (yesterday - today))TrueUnlike -, this one has no direction to get wrong — Section 7 shows why.
6. The Originals Are Untouched
Every operation in this chapter builds a new set and changes neither operand:
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
new_fails = today - yesterday
both = today & yesterday
either = today | yesterday
print(sorted(today))
print(sorted(yesterday))
print(len(today), len(yesterday))['crc', 'interrupt', 'timeout']
['crc', 'reset', 'timeout']
3 3Three and three, exactly as they started, after three operations.
This is worth noticing because Chapter 7.3 was entirely about changing a set in place. These are operators, not methods, and they behave like every operator in Module 3: they take values and produce a value. today - yesterday no more changes today than a - b changes a.
7. Which Operations Have a Direction
Three of the four give the same answer whichever way round you write them. One does not:
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
print((today & yesterday) == (yesterday & today))
print((today | yesterday) == (yesterday | today))
print((today - yesterday) == (yesterday - today))True
True
FalseIntersection and union are symmetric. "In both" and "in either" are questions about the pair, so the order of the operands cannot matter.
Difference is not. "In this one and not that one" names a specific side, so swapping asks a genuinely different question.
Symmetric difference is symmetric too — the name says so, and "in one but not both" does not single out a side.
That is a rule you can derive rather than memorise: ask whether the English question mentions a particular set. If it does, the operation has a direction.
8. The Symbols, Collected
Now that each has arrived with a question attached:
| Question | Operator | Name |
|---|---|---|
In A but not B | A - B | difference |
| In both | A & B | intersection |
| In either | A | B | union |
| In one but not both | A ^ B | symmetric difference |
Every one returns a new set and leaves both operands alone. And of the four, only difference has a direction — Section 7 proved that with the other three giving the same answer either way round.
9. Operators and Methods
Each operation also has a method form:
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
print(sorted(today.difference(yesterday)))
print(sorted(today.intersection(yesterday)))
print(sorted(today.union(yesterday)))
print(sorted(today.symmetric_difference(yesterday)))['interrupt']
['crc', 'timeout']
['crc', 'interrupt', 'reset', 'timeout']
['interrupt', 'reset']Identical results. The method names also make the direction explicit — today.difference(yesterday) reads as "today's, less yesterday's", which some people find harder to get backwards than today - yesterday.
There is one real difference between the two forms. Operators require sets on both sides:
print({"a", "b"} - ["a"])TypeError: unsupported operand type(s) for -: 'set' and 'list'print({"a"} | ["b"])TypeError: unsupported operand type(s) for |: 'set' and 'list'Methods accept any iterable:
today = {"timeout", "crc", "interrupt"}
print(sorted(today.difference(["timeout"])))['crc', 'interrupt']A list on the right, and it worked. That is genuinely useful when the other collection arrived as a list and you do not want to convert it first.
Operations chain, since each produces a set:
yesterday = {"reset", "timeout", "crc"}
today = {"timeout", "crc", "interrupt"}
print(sorted((today | yesterday) - {"crc"}))['interrupt', 'reset', 'timeout']Everything involved in either run, except crc.
10. Predict, Run, Observe
Predict every line before reading on. Two of these are the direction trap.
a = {"x", "y", "z"}
b = {"y", "z", "w"}
print(sorted(a - b))
print(sorted(b - a))
print(sorted(a & b))
print(len(a | b))
print(sorted(a ^ b))
print((a - b) == (b - a))
print((a & b) == (b & a))
print(len(a), len(b))The verified output:
['x']
['w']
['y', 'z']
4
['w', 'x']
False
True
3 3a - b is ['x'] and b - a is ['w'] — both single elements, completely different meanings. That is Section 3.
a & b is the two shared elements. len(a | b) is 4, not 6: y and z are counted once.
a ^ b is ['w', 'x'] — exactly the two difference results combined, the elements that are in one set only.
The two booleans confirm Section 7: difference is directional, intersection is not.
And the last line shows 3 3 — after five operations, both originals are untouched.
11. Exercises
Exercise 1 — the four questions. Given:
yesterday = {"reset", "crc", "timeout"}
today = {"crc", "interrupt"}Without running Python:
- Which expression gives the newly failing tests?
- Which gives the tests that were fixed?
- Which gives the failures common to both runs?
- Predict the number of elements in each of those three results.
- Run the code and verify all three with
sorted()and==.
Exercise 2 — predict. For p = {1, 2, 3} and q = {3, 4}, give each result, then verify:
(a) p - q (b) q - p (c) p & q (d) p | q (e) p ^ q (f) len(p | q) (g) len(p & q)
Exercise 3 — name the question. For each expression, write the English question it answers for two sets of failing tests:
(a) today & yesterday (b) yesterday - today (c) today | yesterday (d) today ^ yesterday
Exercise 4 — the backwards report. A colleague's script reports new failures using yesterday - today. Explain in three sentences what it actually reports, why no error occurs, and what a reviewer would wrongly conclude.
Exercise 5 — symmetric or not. Without running anything, say which of these are True and explain the odd one out in two sentences:
(a) (a | b) == (b | a) (b) (a & b) == (b & a) (c) (a - b) == (b - a) (d) (a ^ b) == (b ^ a)
Exercise 6 — untouched. Explain in two sentences why c = a - b leaves a unchanged, and name the chapter whose methods behave the opposite way.
Exercise 7 — operator or method. For each, say which form you must or would use and why:
(a) both collections are sets, and you want everything in either
(b) yesterday is a set and today_list is a list, and you want what is new
(c) you want a chained expression removing one name from a union
Exercise 8 — read the error. Run {"a"} - ["a"], write down the exact message, and give two different ways to fix it.
Exercise 9 — build the review. Using the data from Exercise 1, write the four lines a review script would print, each labelled, each using sorted() so the report is reproducible. Say in one sentence why sorted() matters for a report specifically.
12. Summary
- Learn the questions, then the symbols. Four operations answer four plain questions about two groups.
A - Bis difference: inA, not inB. It is directional —today - yesterdayis what broke,yesterday - todayis what got fixed, and nothing in the output distinguishes them.A & Bis intersection: in both.A | Bis union: in either, each value once. It is not list concatenation — duplicates collapse and there is no order.A ^ Bis symmetric difference: in one but not both — everything that changed.- Intersection, union and symmetric difference are symmetric; difference is not. If the English question names a particular set, the operation has a direction.
- Every operation returns a new set and leaves both operands untouched, unlike the in-place methods of Chapter 7.3.
- Methods match the operators —
difference,intersection,union,symmetric_difference— and accept any iterable, while the operators require sets on both sides. - Check results with
sorted(),len()or==against an expected set, never by printing the set itself.
The next chapter asks about the relationship between two sets rather than computing a third.
Where this fits
Part of the Python curriculum.
