Volume 3: Discrete and Probabilistic Mathematics · Part VI: Discrete Mathematics

Chapter 21. Logic and Sets

Every if statement you have ever written is a small piece of logic, and every database query is a small piece of set theory. This chapter makes that implicit knowledge explicit: propositions and the connectives that combine them, the slippery conditional "if …​ then", the laws (De Morgan’s above all) that let you rewrite conditions safely, the quantifiers "for all" and "there exists", and then sets, relations and the special relations called functions. By the end you will have the vocabulary that the rest of discrete mathematics, and every proof in this book, is written in.

Where this chapter sits

Prerequisites: comfort with numbers and basic algebra from Chapter 1, Chapter 2 and Chapter 4. Nothing from calculus, geometry or trigonometry is needed. Discrete mathematics can be studied alongside algebra.

Unlocks: Chapter 22 (proof techniques are logic in action), Chapter 23 (congruence is an equivalence relation), Chapter 24 (counting is set theory with numbers), Chapter 25 (a graph is a set with a relation on it), and Chapter 29 (events are sets). It also sharpens your reading of Chapter 11, where functions are defined as special relations.

CS payoff: boolean expressions, short-circuit evaluation, refactoring conditionals, digital logic, SQL, Python sets, hashing contracts, dependency ordering and SAT solving.

Why this chapter matters. Outside code, the same few ideas decide whether a contract clause has been met, whether a positive medical test means what the patient hopes it means, which blood a patient can safely receive, and how many people on a survey ticked "neither". Logic is the grammar of precise rules; sets are the bookkeeping of who or what a rule applies to.

21.1. Propositions and connectives

21.1.1. The problem

A product manager writes in a ticket: "Show the discount banner if the user is a member or has spent over $100, but not if they’re logged out." You translate that into code. A week later a bug report arrives: logged-out members are seeing the banner. Was the ticket ambiguous, or was the code wrong?

English is a wonderful language for poetry and a terrible one for specifications. "Or" sometimes means "one or the other or both" and sometimes "one or the other but not both". "But" means "and" with an attitude. "Unless" means something different to almost everyone who reads it. Logic is the discipline of removing that ambiguity: it gives each connecting word one precise meaning, and then gives rules for combining and rewriting statements without changing what they say.

21.1.2. Intuition

Think of a statement as a wire that carries either true or false. Connectives are little machines that take one or two wires in and produce one wire out. "Not" flips its wire. "And" outputs true only if both inputs are true. "Or" outputs true if at least one input is true. Because each wire has only two possible values, you can describe any machine completely by listing what it does for every combination of inputs. That list is a truth table, and it is the whole definition: no further interpretation is needed or allowed.

21.1.3. Vocabulary and notation

Proposition

A proposition (or statement) is a declarative sentence that is either true or false, but not both. "7 is prime" is a proposition (true). "7 is even" is a proposition (false). "Close the door" (a command) and "\(x > 3\)" (whose truth depends on an unknown \(x\)) are not propositions. We name propositions with lowercase letters \(p, q, r\). The truth value of a proposition is T (true) or F (false), sometimes written 1 and 0.

Connective

A logical connective (or operator) builds a new proposition from one or more existing ones. The five standard connectives are:

Symbol Name Read as In code

\(\neg p\)

negation

"not \(p\)"

not p, !p

\(p \wedge q\)

conjunction

"\(p\) and \(q\)"

p and q, p && q

\(p \vee q\)

disjunction

"\(p\) or \(q\)"

p or q, p || q

\(p \to q\)

conditional (implication)

"if \(p\) then \(q\)"

(not p) or q

\(p \leftrightarrow q\)

biconditional

"\(p\) if and only if \(q\)"

p == q

A proposition built from others using connectives is compound; one that cannot be broken down further is atomic. Some books write \(\sim p\) or \(\overline{p}\) for negation, \(\Rightarrow\) for \(\to\), and \(\Leftrightarrow\) or "iff" for \(\leftrightarrow\).

The symbols \(\wedge\) (and) and \(\vee\) (or) are easy to mix up. A mnemonic: \(\wedge\) looks like the "A" in *A*nd with the crossbar missing; \(\vee\) comes from the Latin vel, "or".

21.1.4. The truth tables

Here are the definitions, all in one table. Each row is one possible combination of truth values for \(p\) and \(q\); with two inputs there are \(2^2 = 4\) rows.

Table 1. Truth tables of the five connectives
\(p\) \(q\) \(\neg p\) \(p \wedge q\) \(p \vee q\) \(p \to q\) \(p \leftrightarrow q\)

T

T

F

T

T

T

T

T

F

F

F

T

F

F

F

T

T

F

T

T

F

F

F

T

F

F

T

T

Three of these columns match everyday intuition. Two need comment.

Or is inclusive. \(p \vee q\) is true when both are true. If you mean "one or the other but not both", that is a different connective, exclusive or, written \(p \oplus q\) (and p != q or p ^ q in most languages). Mathematics, logic and programming languages all default to the inclusive reading.

The conditional is the subject of the next section; its last two rows surprise almost everyone.

Exclusive or (XOR)

\(p \oplus q\) ("\(p\) xor \(q\)") is true when exactly one of \(p, q\) is true. It equals \((p \vee q) \wedge \neg(p \wedge q)\), and it is the negation of the biconditional: \(p \oplus q \equiv \neg(p \leftrightarrow q)\).

21.1.5. Worked example: building a truth table

Let us settle the banner ticket. Let \(m\) = "user is a member", \(s\) = "user spent over $100", \(\ell\) = "user is logged in". The most reasonable reading is Try it: Build the banner truth table.

\[(m \vee s) \wedge \ell .\]

With three atomic propositions there are \(2^3 = 8\) rows. The systematic way to list them is to count down in binary: the first column alternates in blocks of four, the second in blocks of two, the third every row. Then compute intermediate columns left to right, exactly as you would evaluate a sub-expression before the expression that uses it.

\(m\) \(s\) \(\ell\) \(m \vee s\) \((m \vee s) \wedge \ell\)

T

T

T

T

T

T

T

F

T

F

T

F

T

T

T

T

F

F

T

F

F

T

T

T

T

F

T

F

T

F

F

F

T

F

F

F

F

F

F

F

Rows 2 and 4 are the bug report: a member who is logged out. The specification says F. The most likely culprit is code written as is_member or big_spender and logged_in, which Python parses as \(m \vee (s \wedge \ell)\) because and binds more tightly than or. In rows 2 and 4 that gives \(T \vee (\ldots) = T\): the banner appears. A truth table makes this kind of disagreement impossible to miss.

Precedence is not a matter of taste

The conventional precedence, from tightest to loosest, is \(\neg\), then \(\wedge\), then \(\vee\), then \(\to\), then \(\leftrightarrow\). So \(\neg p \vee q \wedge r\) means \((\neg p) \vee (q \wedge r)\). Programming languages agree on "not before and before or", but whenever an expression mixes and with or, add parentheses. They cost nothing and they document intent.

Example 109. Procedure — building a truth table

Input: a compound proposition with \(n\) distinct atomic propositions.

Steps:

  1. List the atomic propositions as the first \(n\) columns.

  2. Write \(2^n\) rows. Fill column \(k\) (counting from 1) with alternating blocks of T and F of length \(2^{n-k}\). This enumerates every combination exactly once.

  3. Identify the sub-expressions, innermost first, and give each its own column.

  4. Fill each new column row by row, using the definitions of the connectives and the columns already computed.

  5. The last column is the truth value of the whole proposition.

Output: the complete truth table; the rows marked T are exactly the situations in which the proposition holds.

The procedure is an algorithm, so here it is as code. itertools.product([True, False], repeat=n) generates the \(2^n\) rows in exactly the order described in step 2.

from itertools import product

def truth_table(f, names):
    """Print the truth table of f, a function of len(names) booleans."""
    print(" ".join(names), "| result")
    for row in product([True, False], repeat=len(names)):
        cells = " ".join("T" if v else "F" for v in row)
        print(cells, "|", "T" if f(*row) else "F")

def implies(p, q):
    return (not p) or q

truth_table(lambda p, q: implies(p, q), ["p", "q"])
p q | result
T T | T
T F | F
F T | T
F F | T

Truth tables are exhaustive, which is both their virtue and their limit: \(n\) variables need \(2^n\) rows. Ten variables means 1,024 rows; forty means about a trillion. That exponential wall is exactly why the algebraic laws later in this chapter, and the SAT solvers mentioned at the end, matter.

Logic gates: connectives in silicon

Every connective is also a physical circuit. A logic gate is a tiny arrangement of transistors whose output voltage is high (1) or low (0) according to a truth table.

Circuit symbols for AND OR XOR and NOT gates
Figure 190. The four basic gates and the connective each computes

Wire gates together and you can compute arithmetic. Adding two one-bit numbers \(A\) and \(B\) gives a sum bit and a carry bit: \(1 + 1 = 10_2\), so the sum bit is 0 and the carry is 1. Reading the truth table, the sum bit is \(A \oplus B\) and the carry is \(A \wedge B\). That two-gate circuit is a half adder; chain full adders (which also accept an incoming carry) and you have the adder inside every CPU.

Half adder built from an XOR gate and an AND gate
Figure 191. A half adder: logic that does arithmetic

A striking fact: the single connective NAND, \(\neg(p \wedge q)\), can build every other connective. \(\neg p\) is \(p \text{ NAND } p\), and \(p \wedge q\) is the NAND of \((p \text{ NAND } q)\) with itself. Chip fabrication exploits this: a whole processor can be laid out from one kind of gate.

Boole, Shannon and the most important master’s thesis

George Boole published The Laws of Thought in 1854, treating logic as algebra with values 0 and 1. For eighty years it was a philosophical curiosity. Then in 1937 a 21-year-old MIT student, Claude Shannon, noticed in his master’s thesis that relay switching circuits obey exactly Boole’s laws. Simplifying the algebra simplified the circuit. That thesis is often called the most consequential master’s thesis ever written: it is the reason the type is called bool.

21.1.6. Where you’d use this

Everyday: rules with several conditions. A shop offers "free delivery if you are a member, or if the order is at least $40 and not oversized". With \(m\), \(f\) (at least $40) and \(o\) (oversized), that is \(m \vee (f \wedge \neg o)\). The brackets matter: \((m \vee f) \wedge \neg o\) is a different policy that charges members for oversized parcels. Writing the formula is how you settle the argument at the checkout.

Medicine: "at least two of three". The qSOFA bedside screen for sepsis flags a patient when at least two of these hold: breathing rate at least 22 per minute (\(r\)), altered mental state (\(a\)), systolic blood pressure at most 100 mmHg (\(b\)). As a formula: \((r \wedge a) \vee (r \wedge b) \vee (a \wedge b)\), the majority function. Its truth table is T in exactly 4 of the 8 rows (TTT, TTF, TFT, FTT), and the same function is the carry-out bit of a full adder.

Engineering: interlocks. An industrial press may cycle only if the guard is closed (\(g\)) and both of the operator’s hands are on the two buttons (\(\ell\), \(r\)): \(g \wedge \ell \wedge r\). Two-hand controls exist so that no hand can be under the ram; the safety argument is literally a truth table with one T row out of eight.

Computing. Search boxes that accept jaguar AND (engine OR gearbox) NOT cat are evaluating a propositional formula per document; firewall and feature-flag rules are the same thing.

21.1.7. Exercises

  1. Which of the following are propositions? (a) "17 is prime." (b) "Is 17 prime?" (c) "\(x^2 \geq 0\) for every real number \(x\)." (d) "\(x + 1 = 5\)."

  2. Build the truth table for \(\neg(p \wedge q)\) and for \(\neg p \wedge \neg q\). Are they the same?

  3. A login is allowed if the password is correct and either the device is trusted or a one-time code was entered. Write this as a formula using \(w\) (password correct), \(d\) (trusted device), \(c\) (code entered), and find every row of its truth table that is T.

Answers
  1. (a) Yes, true. (b) No, a question. (c) Yes, true (the variable is bound by "for every", so the sentence as a whole is true or false; see Section 21.4). (d) No: its truth depends on the unknown \(x\). It is a predicate.

  2. \(\neg(p \wedge q)\) has column F, T, T, T (rows TT, TF, FT, FF). \(\neg p \wedge \neg q\) has column F, F, F, T. They differ in rows TF and FT, so they are not the same. (The correct partner of \(\neg(p \wedge q)\) is \(\neg p \vee \neg q\); see De Morgan’s laws.)

  3. \(w \wedge (d \vee c)\). It is T exactly when \(w\) is T and at least one of \(d, c\) is T: rows (\(w,d,c\)) = (T,T,T), (T,T,F), (T,F,T). Three of eight rows.

US curriculum

Truth tables and connectives are not a standard part of the core US high-school sequence. They appear briefly in many Geometry courses (grade 9–10) in a unit on "reasoning and proof", in AP Computer Science Principles and AP Computer Science A (boolean expressions), and in full in a first-year college Discrete Mathematics course.

21.2. The conditional: "if …​ then"

21.2.1. The problem

A parent says: "If you finish your homework, you can play video games." The child does not finish the homework and plays anyway. Did the parent lie? Most people say: no, the parent made no promise about what happens if the homework is unfinished.

Now look at the truth table for \(p \to q\) again. When \(p\) is false, \(p \to q\) is true, whatever \(q\) is. "False implies anything." This feels like a cheat when you first meet it. Why is it the right definition?

21.2.2. Intuition: a conditional is a promise

Read \(p \to q\) as a promise or a contract: "whenever \(p\) holds, I guarantee \(q\)". The only way to break such a promise is for \(p\) to happen and \(q\) not to: row T, F. In every other row the promise has not been broken, and in two-valued logic "not false" means true.

A better test case than homework is a rule that has to hold for many cases at once. Take "for every whole number \(n\), if \(n\) is divisible by 4 then \(n\) is even". Everyone agrees this is true. Now check it at \(n = 3\): "if 3 is divisible by 4 then 3 is even" has a false hypothesis and a false conclusion. At \(n = 6\): "if 6 is divisible by 4 then 6 is even" has a false hypothesis and a true conclusion. If either of these instances counted as false, then the general rule, which is the conjunction of all its instances, would be false. So for universal rules to work the way everyone wants, the conditional with a false hypothesis must be true. Logic is designed so that general statements behave; the truth table follows.

Vacuous truth

A conditional whose hypothesis is false is vacuously true: true because there is no case that could break it. "Every unicorn in this room is purple" is vacuously true, since there are no unicorns to check. In code, all(x > 100 for x in []) returns True for the same reason.

Hypothesis, conclusion, sufficient, necessary

In \(p \to q\), \(p\) is the hypothesis (or antecedent, or premise) and \(q\) is the conclusion (or consequent). All of the following say exactly \(p \to q\):

  • if \(p\) then \(q\); \(q\) if \(p\);

  • \(p\) implies \(q\);

  • \(p\) only if \(q\);

  • \(p\) is sufficient for \(q\);

  • \(q\) is necessary for \(p\);

  • \(q\) whenever \(p\); not \(p\) unless \(q\).

The biconditional \(p \leftrightarrow q\) ("\(p\) if and only if \(q\)", often "iff") means \((p \to q) \wedge (q \to p)\): each is necessary and sufficient for the other.

"Only if" deserves a second look because it trips everyone. "You can vote only if you are 18 or over" means voting \(\to\) 18-or-over: being 18 is necessary. It does not say every 18-year-old can vote (they might not be registered).

21.2.3. Why it works: the conditional as an "or"

Comparing columns shows that \(p \to q\) has exactly the same truth table as \(\neg p \vee q\):

\(p\) \(q\) \(\neg p\) \(\neg p \vee q\) \(p \to q\)

T

T

F

T

T

T

F

F

F

F

F

T

T

T

T

F

F

T

T

T

"If it is raining, I take an umbrella" says the same as "either it is not raining, or I take an umbrella". This is how you implement implication in a language that has no implication operator, and it is the form that algebraic simplification works with.

21.2.4. Converse, inverse, contrapositive

From one conditional \(p \to q\) you can form three relatives by swapping and/or negating the parts.

Name Form Example (\(p\): "it is a square", \(q\): "it is a rectangle")

original

\(p \to q\)

If it is a square, it is a rectangle. (true)

converse

\(q \to p\)

If it is a rectangle, it is a square. (false)

inverse

\(\neg p \to \neg q\)

If it is not a square, it is not a rectangle. (false)

contrapositive

\(\neg q \to \neg p\)

If it is not a rectangle, it is not a square. (true)

The pattern in the example is universal: a conditional is always equivalent to its contrapositive, and the converse is always equivalent to the inverse (the inverse is the contrapositive of the converse). But a conditional is not equivalent to its converse. Check the contrapositive with a truth table:

\(p\) \(q\) \(p \to q\) \(\neg q\) \(\neg p\) \(\neg q \to \neg p\)

T

T

T

F

F

T

T

F

F

T

F

F

F

T

T

F

T

T

F

F

T

T

T

T

The third and last columns agree in every row. The contrapositive is not a curiosity: it is a whole proof technique in Chapter 22, used whenever "not \(q\)" is easier to work with than \(p\).

A picture makes the asymmetry obvious. Treat \(p\) and \(q\) as properties and draw the set of things having each. "Every square is a rectangle" means the square region sits entirely inside the rectangle region. The converse would need the reverse containment, and the non-square rectangle on the right shows it fails.

Euler diagram with the set of squares drawn inside the set of rectangles; a square, a non-square rectangle and a triangle illustrate the three possible truth rows; the region inside squares but outside rectangles is empty
Figure 192. \(p \to q\) as containment: the only row that would break the promise (\(p\) true, \(q\) false) is the empty crescent of P lying outside Q
Affirming the consequent

Confusing a statement with its converse is the most common logical error in everyday reasoning, in debugging, and in medicine. "If the server is overloaded, requests time out. Requests are timing out. So the server is overloaded." This is invalid: it uses \(q \to p\), which was never established. Timeouts could come from a network partition. The valid inference patterns are

  • modus ponens: from \(p \to q\) and \(p\), conclude \(q\);

  • modus tollens: from \(p \to q\) and \(\neg q\), conclude \(\neg p\) (no timeouts, so not overloaded).

The invalid ones are affirming the consequent (from \(q\), conclude \(p\)) and denying the antecedent (from \(\neg p\), conclude \(\neg q\)).

21.2.5. Worked example: the four relatives of a code comment

A comment says: "If the cache is warm, the request returns in under 10 ms." Let \(w\) = "cache is warm", \(f\) = "request is fast". Then:

\[\begin{aligned} \text{statement:}\quad & w \to f &&\text{warm} \Rightarrow \text{fast}\\ \text{contrapositive:}\quad & \neg f \to \neg w &&\text{slow} \Rightarrow \text{cold (equivalent; useful for diagnosis)}\\ \text{converse:}\quad & f \to w &&\text{fast} \Rightarrow \text{warm (NOT implied: a cold cache can still be fast)}\\ \text{inverse:}\quad & \neg w \to \neg f &&\text{cold} \Rightarrow \text{slow (NOT implied, equivalent to the converse)} \end{aligned}\]

If you observe a slow request, the contrapositive lets you conclude the cache was cold, assuming the comment is true. If you observe a fast request, you can conclude nothing about the cache.

The four-card puzzle

Psychologists have measured how bad people are at this. In the Wason selection task (1966), subjects see four cards showing E, K, 4, 7 and are told "if a card has a vowel on one side, it has an even number on the other". Which cards must you turn over to test the rule? Fewer than 10% answer correctly: E and 7. The 7 is the contrapositive check (odd, so must not be a vowel). Most people pick E and 4, and the 4 is useless: it tests the converse.

21.2.6. Where you’d use this

Everyday: reading offers and rules. "If you spend over $50, delivery is free" is broken only by a customer who spends $60 and is charged for delivery. The shop that also gives free delivery on a $30 order has not broken it (false hypothesis). "Only if" clauses are the ones people misread: "the warranty is valid only if the device has not been opened" means valid \(\to\) not opened. Not opening it is necessary, but not sufficient: a two-year-old device may be out of warranty anyway.

Medicine: a test and its converse. "If you have the disease, the test is positive" (a test with high sensitivity) is the statement labs measure. The patient wants the converse, "if the test is positive, I have the disease", and that can be badly false when the disease is rare. Quantifying how false is a job for Bayes' theorem in Chapter 29; recognising that it is a different statement is a job for this section.

Engineering: fault-finding with the contrapositive. "If the fuse is intact and the switch is closed, the lamp lights": \((f \wedge s) \to L\). The lamp is dark, so by modus tollens \(\neg(f \wedge s)\), which by De Morgan (next section) is \(\neg f \vee \neg s\): the fuse has blown or the switch is open. Check the switch first; it is cheaper.

Computing. An assert or a function’s documented precondition is a conditional: "if the input is sorted, the result is the index of the target". Calling it on unsorted input does not make the function wrong; it makes the contract vacuous, and the result meaningless.

21.2.7. Exercises

  1. Write the converse, inverse and contrapositive of "If \(n\) is divisible by 6, then \(n\) is divisible by 3." Which are true for all whole numbers \(n\)?

  2. Rewrite "The build passes only if all tests pass" as an "if …​ then" sentence.

  3. Is "if \(2 + 2 = 5\), then the moon is made of cheese" true or false? Why?

  4. Express \(p \leftrightarrow q\) using only \(\neg\), \(\wedge\) and \(\vee\).

Answers
  1. Converse: if \(n\) is divisible by 3, then \(n\) is divisible by 6 (false: \(n = 9\)). Inverse: if \(n\) is not divisible by 6, then \(n\) is not divisible by 3 (false: \(n = 9\) again). Contrapositive: if \(n\) is not divisible by 3, then \(n\) is not divisible by 6 (true, equivalent to the original, which is true because \(6 = 2 \cdot 3\)).

  2. "If the build passes, then all tests pass." "Only if" introduces the conclusion.

  3. True, vacuously: the hypothesis \(2 + 2 = 5\) is false.

  4. \(p \leftrightarrow q \equiv (p \to q) \wedge (q \to p) \equiv (\neg p \vee q) \wedge (\neg q \vee p)\). An equally good answer is \((p \wedge q) \vee (\neg p \wedge \neg q)\): "both true or both false".

US curriculum

Conditionals, converse, inverse and contrapositive are a standard unit in high-school Geometry (grade 9–10), under "conditional statements" and "biconditionals", where they set up two-column proofs. Vacuous truth is usually postponed to college Discrete Mathematics.

21.3. Logical equivalence and De Morgan’s laws

21.3.1. The problem

Here is a real-world conditional from a code review:

if not (user.is_admin or (user.is_owner and not doc.locked)):
    raise PermissionDenied

What does it actually say? Under what conditions is the user allowed? And can it be written so that a tired reviewer at 6 p.m. can check it? We need a way to transform logical expressions while guaranteeing the meaning does not change.

21.3.2. Intuition

Two expressions are equivalent if no situation can tell them apart: in every row of the truth table they give the same answer. Just as algebra has laws such as \(a(b + c) = ab + ac\) that let you rewrite expressions, logic has laws that let you rewrite propositions. Each law can be verified once, by truth table, and then used forever without checking again.

Logical equivalence, tautology, contradiction

Two propositions \(A\) and \(B\) are logically equivalent, written \(A \equiv B\), if they have identical truth tables. Equivalently, \(A \leftrightarrow B\) is true in every row.

A tautology is a proposition that is true in every row (for example \(p \vee \neg p\)). A contradiction is false in every row (for example \(p \wedge \neg p\)). A proposition that is neither is contingent. A proposition is satisfiable if at least one row makes it true.

\(\equiv\) is a statement about two propositions (a "meta" claim), not a connective inside one.

21.3.3. The laws

Table 2. The laws of propositional logic (\(\mathbf{T}\) is any tautology, \(\mathbf{F}\) any contradiction)
Name "And" form "Or" form

Identity

\(p \wedge \mathbf{T} \equiv p\)

\(p \vee \mathbf{F} \equiv p\)

Domination

\(p \wedge \mathbf{F} \equiv \mathbf{F}\)

\(p \vee \mathbf{T} \equiv \mathbf{T}\)

Idempotent

\(p \wedge p \equiv p\)

\(p \vee p \equiv p\)

Complement

\(p \wedge \neg p \equiv \mathbf{F}\)

\(p \vee \neg p \equiv \mathbf{T}\)

Double negation

\(\neg\neg p \equiv p\)

Commutative

\(p \wedge q \equiv q \wedge p\)

\(p \vee q \equiv q \vee p\)

Associative

\((p \wedge q) \wedge r \equiv p \wedge (q \wedge r)\)

\((p \vee q) \vee r \equiv p \vee (q \vee r)\)

Distributive

\(p \wedge (q \vee r) \equiv (p \wedge q) \vee (p \wedge r)\)

\(p \vee (q \wedge r) \equiv (p \vee q) \wedge (p \vee r)\)

De Morgan

\(\neg(p \wedge q) \equiv \neg p \vee \neg q\)

\(\neg(p \vee q) \equiv \neg p \wedge \neg q\)

Absorption

\(p \wedge (p \vee q) \equiv p\)

\(p \vee (p \wedge q) \equiv p\)

Conditional

\(p \to q \equiv \neg p \vee q \equiv \neg q \to \neg p\)

Negated conditional

\(\neg(p \to q) \equiv p \wedge \neg q\)

Notice the pattern: every law comes in a pair, obtained by swapping \(\wedge\) with \(\vee\) and \(\mathbf{T}\) with \(\mathbf{F}\). This is the duality principle. Also notice that unlike ordinary algebra, both distributive laws hold: "and" distributes over "or" (like multiplication over addition), and "or" also distributes over "and" (which has no arithmetic counterpart: \(a + bc \neq (a + b)(a + c)\) in general).

21.3.4. De Morgan’s laws, in words

\[\neg(p \wedge q) \equiv \neg p \vee \neg q \qquad\qquad \neg(p \vee q) \equiv \neg p \wedge \neg q\]

To push a "not" inside a bracket, negate each piece and flip the connective. "It is not the case that the server is both up and healthy" means "the server is down or it is unhealthy". "It is not the case that it’s raining or snowing" means "it is not raining and it is not snowing".

Why must the connective flip? "Not both" fails only when both hold, so it is true as soon as one of them fails: that is an "or" of failures. "Neither" requires every option to fail: that is an "and" of failures. You saw the truth tables in the first exercise set; the Venn diagram in Section 21.5 gives the same picture for sets.

Example 110. Procedure — simplifying a boolean expression

Input: a boolean expression, typically a messy condition.

Steps:

  1. Replace every \(\to\) by \(\neg p \vee q\) and every \(\leftrightarrow\) by \((\neg p \vee q) \wedge (\neg q \vee p)\), so that only \(\neg, \wedge, \vee\) remain.

  2. Push every \(\neg\) inward with De Morgan’s laws, flipping \(\wedge \leftrightarrow \vee\) each time, until negations sit directly on atomic propositions.

  3. Remove double negations: \(\neg\neg p \equiv p\).

  4. Simplify with the complement, identity, domination, idempotent and absorption laws, using distribution or factoring where it shortens the expression.

  5. Check the result: either compare truth tables (for few variables) or test it mechanically, for example with the equivalent function below.

Output: an equivalent, simpler expression, usually with negations only on single variables.

21.3.5. Worked example: the permission check

Let \(a\) = user.is_admin, \(o\) = user.is_owner, \(\ell\) = doc.locked. The code raises when Try it: Push the negation inward.

\[\neg\big(a \vee (o \wedge \neg \ell)\big).\]

Push the negation inward one step at a time:

\[\begin{aligned} \neg\big(a \vee (o \wedge \neg \ell)\big) &\equiv \neg a \wedge \neg(o \wedge \neg \ell) &&\text{(De Morgan on } \vee\text{)}\\ &\equiv \neg a \wedge (\neg o \vee \neg\neg \ell) &&\text{(De Morgan on } \wedge\text{)}\\ &\equiv \neg a \wedge (\neg o \vee \ell) &&\text{(double negation)} \end{aligned}\]

In words: deny if the user is not an admin, and either is not the owner or the document is locked. The code can be rewritten

if not user.is_admin and (not user.is_owner or doc.locked):
    raise PermissionDenied

Even better is to name the positive condition, since the original’s inner expression was the allow rule all along:

can_edit = user.is_admin or (user.is_owner and not doc.locked)
if not can_edit:
    raise PermissionDenied

Which is better is a judgment call; the point is that you can now move between the forms with certainty.

21.3.6. A second worked example: absorption

Simplify \((p \wedge q) \vee (p \wedge \neg q)\). Try it: Check the absorption example.

\[\begin{aligned} (p \wedge q) \vee (p \wedge \neg q) &\equiv p \wedge (q \vee \neg q) &&\text{(distributive law, read right to left: factor out } p\text{)}\\ &\equiv p \wedge \mathbf{T} &&\text{(complement)}\\ &\equiv p &&\text{(identity)} \end{aligned}\]

Sanity check in words: "\(p\) and \(q\), or \(p\) and not \(q\)" is true exactly when \(p\) is true, whatever \(q\) does. The variable \(q\) was irrelevant, and the code if (p and q) or (p and not q) is a smell.

21.3.7. Alternative approaches and choosing a method

You have three ways to decide whether two expressions are equivalent.

  • Truth table. Mechanical and certain. Best for up to three or four variables. Hopeless beyond about twenty.

  • Algebraic laws. Produces a simplified form and an explanation of why, which a truth table never gives you. Requires some skill to choose which law to apply.

  • Mechanical check in code. The same as a truth table but delegated to a computer, which pushes the limit to about 20–25 variables:

from itertools import product

def equivalent(f, g, n):
    """True if f and g agree on every one of the 2**n input rows."""
    return all(f(*row) == g(*row) for row in product([True, False], repeat=n))

def implies(p, q):
    return (not p) or q

# De Morgan
print(equivalent(lambda p, q: not (p and q), lambda p, q: (not p) or (not q), 2))
# contrapositive vs converse
print(equivalent(lambda p, q: implies(p, q), lambda p, q: implies(not q, not p), 2))
print(equivalent(lambda p, q: implies(p, q), lambda p, q: implies(q, p), 2))
# distributive law, three variables
print(equivalent(lambda p, q, r: p and (q or r),
                 lambda p, q, r: (p and q) or (p and r), 3))
True
True
False
True

Rule of thumb: use the laws to find a simpler form, then use a truth table or equivalent to confirm it. When refactoring a production conditional, write the confirmation as a unit test.

The classic De Morgan slip

The two errors that cause real bugs:

  • Negating the pieces but forgetting to flip the connective: writing not (a and b) as not a and not b. That is "neither", not "not both".

  • Negating a range check incorrectly. "\(x\) is in range" is \(0 \le x \wedge x < n\). Its negation is \(x < 0 \vee x \ge n\), with an or. A guard written if x < 0 and x >= n can never fire.

Computer-science connection: short-circuit evaluation

Mathematically \(p \wedge q \equiv q \wedge p\). In code, p and q and q and p can behave differently, because almost every language evaluates and/or lazily: left to right, stopping as soon as the answer is known.

  • False and X: the answer is False whatever X is (domination law), so X is never evaluated.

  • True or X: the answer is True whatever X is, so X is never evaluated.

def expensive():
    print("  expensive() was called")
    return True

print("False and expensive():")
r = False and expensive()
print("True or expensive():")
r = True or expensive()
print("True and expensive():")
r = True and expensive()
False and expensive():
True or expensive():
True and expensive():
  expensive() was called

This is why if user is not None and user.is_admin is safe while the reversed order crashes, and why you should put cheap, likely-to-decide tests first. Commutativity holds for the values; it does not hold for side effects, exceptions or cost. When you apply the laws of logic to code, check that no operand has side effects or can raise, or keep guarding conditions on the left.

Python adds one more twist: and/or return one of their operands, not necessarily a bool. 0 or "default" is "default", the basis of the x = value or default idiom (which misfires when a legitimate value is falsy, such as 0 or "").

Interactive Lab: truth-table playground

Type two boolean expressions in up to five variables. The lab draws both truth tables side by side, highlights rows where they disagree, and offers a "push negation inward" button that applies De Morgan one step at a time, showing which law fired. A challenge mode gives a messy condition from real code and asks for the shortest equivalent.

21.3.8. Where you’d use this

Everyday: rules phrased in the negative. A cinema says "no entry unless you are over 15 or with an adult". Entry is allowed when \(a \vee w\); by De Morgan you are refused exactly when \(\neg a \wedge \neg w\), under 15 and alone. Spreadsheets obey the same law: =NOT(OR(B2>100, C2="late")) and =AND(B2⇐100, C2<>"late") filter the same rows, and the second is easier for a colleague to read.

Engineering: fewer gates. Each gate costs chip area, power and a few picoseconds of delay. The absorption example above turned \((p \wedge q) \vee (p \wedge \neg q)\) (two ANDs, a NOT and an OR, four gates) into \(p\) (no gates: a wire). Circuit synthesis tools apply exactly these laws, millions of times, to every chip design.

Computing. Database query optimisers rewrite WHERE NOT (status = 'done' OR archived) into status <> 'done' AND NOT archived so that each part can use an index; compilers push negations inward the same way. The Watch out box above is the bug you get when a human does the rewrite by hand and forgets to flip the or.

21.3.9. Exercises

  1. Use De Morgan’s laws to negate: (a) "the file exists and is readable"; (b) \(x < 0 \vee x > 10\); (c) not (a or not b).

  2. Simplify \(\neg(p \to q) \vee q\) using the laws, showing each step. Try it: Step through this simplification.

  3. Show that \(p \wedge (p \vee q) \equiv p\) (absorption) with a truth table.

  4. Is \((p \to q) \vee (q \to p)\) a tautology, a contradiction, or contingent?

  5. A colleague "simplifies" if not (is_weekend or is_holiday): to if not is_weekend or not is_holiday:. Find inputs where the two disagree and give the correct rewrite. Try it: Find where they disagree.

Answers
  1. (a) The file does not exist or it is not readable. (b) \(x \ge 0 \wedge x \le 10\), i.e. \(0 \le x \le 10\). (c) \(\neg(a \vee \neg b) \equiv \neg a \wedge \neg\neg b \equiv \neg a \wedge b\), i.e. not a and b.

  2. \(\neg(p \to q) \vee q \equiv (p \wedge \neg q) \vee q\) (negated conditional) \(\equiv (p \vee q) \wedge (\neg q \vee q)\) (distribute \(\vee\) over \(\wedge\)) \(\equiv (p \vee q) \wedge \mathbf{T}\) (complement) \(\equiv p \vee q\) (identity).

  3. Rows (\(p,q\)): TT gives \(p \vee q = T\), so \(p \wedge T = T\); TF: \(T \wedge T = T\); FT: \(F \wedge T = F\); FF: \(F \wedge F = F\). The column T, T, F, F equals the \(p\) column.

  4. A tautology. \((\neg p \vee q) \vee (\neg q \vee p) \equiv (\neg p \vee p) \vee (q \vee \neg q) \equiv \mathbf{T} \vee \mathbf{T} \equiv \mathbf{T}\). Surprising but correct: of any two propositions, one implies the other, purely by the truth-table definition.

  5. With is_weekend = True, is_holiday = False: the original gives not True = False; the "simplification" gives False or True = True. Correct rewrite: if not is_weekend and not is_holiday: ("neither").

US curriculum

De Morgan’s laws appear in AP Computer Science A (boolean expressions unit) and sometimes in Geometry logic units. The full set of equivalence laws is standard in college Discrete Mathematics, often taken in the first or second year of a CS degree.

21.4. Predicates and quantifiers

21.4.1. The problem

"\(x > 3\)" is not a proposition, because its truth depends on \(x\). Yet mathematics and code are full of statements like "every element of the list is positive", "some user has no password", "for every input there is a matching output". Propositional logic, with its fixed T/F atoms, cannot express any of these. We need statements with variables in them, and a way to say "all" and "some".

21.4.2. Intuition

A predicate is a proposition with a hole in it, a function that returns a boolean: is_even(n). Fill the hole with a value and you get a proposition. A quantifier fills the hole differently: instead of naming one value, it makes a claim about all values or about at least one. In code, the universal quantifier is all(…​) and the existential quantifier is any(…​).

Predicate and domain

A predicate \(P(x)\) is a statement involving a variable \(x\) that becomes a proposition when \(x\) is given a value. The set of allowed values is the domain (or universe of discourse). With domain the integers, \(P(x)\): "\(x\) is even" gives \(P(4)\) true and \(P(7)\) false. Predicates can have several variables: \(L(x, y)\): "\(x < y\)".

Quantifiers
  • The universal quantifier \(\forall\) ("for all", an upside-down A): \(\forall x\, P(x)\) is true if \(P(x)\) is true for every \(x\) in the domain.

  • The existential quantifier \(\exists\) ("there exists", a backwards E): \(\exists x\, P(x)\) is true if \(P(x)\) is true for at least one \(x\) in the domain.

  • \(\exists!\, x\, P(x)\) means "there exists exactly one \(x\)".

The domain is often written into the quantifier: \(\forall x \in \mathbb{Z}\) ("for every integer \(x\)"). A variable governed by a quantifier is bound; one that is not is free. Only a statement with no free variables is a proposition.

Over a finite domain \(\{a_1, \ldots, a_n\}\), quantifiers are just long connectives:

\[\forall x\, P(x) \;\equiv\; P(a_1) \wedge P(a_2) \wedge \cdots \wedge P(a_n), \qquad \exists x\, P(x) \;\equiv\; P(a_1) \vee P(a_2) \vee \cdots \vee P(a_n).\]

This view explains vacuous truth again: over an empty domain, a "for all" is an empty "and", which is true (the identity for \(\wedge\)), and a "there exists" is an empty "or", which is false (the identity for \(\vee\)). It is the same reason the empty sum is 0 and the empty product is 1.

nums = [3, 8, 12, 5]
print(all(n > 0 for n in nums))        # for all n, n > 0
print(any(n % 2 == 0 for n in nums))   # there exists an even n
print(all(n % 2 == 0 for n in nums))   # for all n, n is even
print(any(n % 2 != 0 for n in nums))   # there exists an n that is not even
print(all(n > 100 for n in []))        # vacuous truth
print(any(n > 100 for n in []))
True
True
False
True
True
False

Lines three and four are negations of each other, which is the next idea.

21.4.3. Negating quantified statements

To deny "every swan is white" you do not need to show that every swan is non-white. You need one black swan. To deny "some student passed" you need to show that every student failed. That is De Morgan’s law again, applied to the long "and" and the long "or":

\[\neg\,\forall x\, P(x) \;\equiv\; \exists x\, \neg P(x) \qquad\qquad \neg\,\exists x\, P(x) \;\equiv\; \forall x\, \neg P(x)\]
Example 111. Procedure — negating a quantified statement

Input: a statement with a string of quantifiers followed by a quantifier-free part.

Steps:

  1. Write \(\neg\) in front of the whole statement.

  2. Move the \(\neg\) rightward past each quantifier in turn, flipping it: \(\forall\) becomes \(\exists\) and \(\exists\) becomes \(\forall\). Domains (\(x \in S\)) stay unchanged.

  3. When the \(\neg\) reaches the quantifier-free part, negate it with the propositional rules: De Morgan for \(\wedge\)/\(\vee\), \(\neg(p \to q) \equiv p \wedge \neg q\) for conditionals, and flip comparisons (\(\neg(a < b)\) is \(a \ge b\)).

  4. Read the result back in English as a check.

Output: an equivalent statement with the negation pushed all the way in.

21.4.4. Worked example: negating a nested statement

Take the statement "every request gets a response within 2 seconds of being sent". With \(R\) the set of requests and \(S\) the set of responses:

\[\forall r \in R\;\; \exists s \in S\;\; \big(\text{answers}(s, r) \wedge t(s) - t(r) \le 2\big).\]

What does a monitoring alert need to detect, i.e. what is the negation?

\[\begin{aligned} &\neg\, \forall r\; \exists s\; \big(\text{answers}(s,r) \wedge t(s) - t(r) \le 2\big)\\ &\equiv \exists r\; \neg\, \exists s\; \big(\text{answers}(s,r) \wedge t(s) - t(r) \le 2\big) &&\text{(flip } \forall \text{ to } \exists\text{)}\\ &\equiv \exists r\; \forall s\; \neg\big(\text{answers}(s,r) \wedge t(s) - t(r) \le 2\big) &&\text{(flip } \exists \text{ to } \forall\text{)}\\ &\equiv \exists r\; \forall s\; \big(\neg\text{answers}(s,r) \vee t(s) - t(r) > 2\big) &&\text{(De Morgan, flip } \le\text{)} \end{aligned}\]

In English: "there is some request such that every response either does not answer it or arrives more than 2 seconds later". Equivalently: some request was not answered in time. You can also write the final disjunction as a conditional, \(\text{answers}(s,r) \to t(s) - t(r) > 2\), "any response that answers it is late".

21.4.5. Order of quantifiers matters

Compare, over the integers, Try it: Now play ∃∀; Play the ∀∃ game.

\[\forall x\; \exists y\; (y > x) \qquad\text{versus}\qquad \exists y\; \forall x\; (y > x).\]

The first says "for every number there is a bigger one": true (take \(y = x + 1\)). The second says "there is one number bigger than every number": false (it would have to be bigger than itself). The only difference is the order. In the first, \(y\) is chosen after \(x\) and may depend on it; in the second, \(y\) must be chosen first, once, and work for all \(x\).

A game helps. Read \(\forall\) as your opponent’s move and \(\exists\) as yours, in left-to-right order. The statement is true if you have a winning strategy. In \(\forall x\, \exists y\), your opponent names \(x\), then you reply: easy. In \(\exists y\, \forall x\), you must commit first, and your opponent picks \(x = y\) to beat you.

Swapping quantifiers silently

"Every user has a password" (\(\forall u\, \exists p\)) is not "there is a password every user has" (\(\exists p\, \forall u\)); the second would be a security disaster. Quantifiers of the same kind commute (\(\forall x\, \forall y \equiv \forall y\, \forall x\)), but \(\forall\) and \(\exists\) do not. Whenever an English sentence has two quantifiers, write it symbolically and check the order. In Chapter 16 the definition of a limit has the shape "\(\forall \varepsilon\; \exists \delta\; \ldots\)" and the order is the whole point.

Computer-science connection: quantifiers everywhere
  • all() and any() in Python, .every() and .some() in JavaScript, allMatch/anyMatch in Java streams, ALL/EXISTS in SQL subqueries are all quantifiers over a finite domain. Like and/or, they short-circuit: any stops at the first true element.

  • A test checks \(P(x)\) for some \(x\). Passing tests show \(\exists x\, P(x)\); a specification claims \(\forall x\, P(x)\). One failing test (a counterexample) refutes the universal claim; a million passing tests do not prove it. Chapter 22 takes this up.

  • Property-based testing (Hypothesis in Python, QuickCheck in Haskell) makes the quantifier explicit: you write the predicate, the library samples the domain hunting for a counterexample.

  • Type signatures are quantified statements: a generic function def first(xs: list[T]) → T claims to work for all types T.

21.4.6. Where you’d use this

Everyday: one counterexample is enough. A dish is safe for a guest with coeliac disease when every ingredient is gluten-free: \(\forall i\, G(i)\). Its negation is \(\exists i\, \neg G(i)\): one ingredient with gluten (soy sauce is the classic hidden one) makes the whole dish unsafe. Allergen labels, "all passengers must have a passport" and "every room has a smoke alarm" are universal claims that a single inspection can refute.

Science and engineering: universal claims and finite tests. A specification such as "for every load up to 5 kN the beam deflects less than 2 mm" is a \(\forall\) statement over infinitely many loads. Testing at 1, 2, 3, 4 and 5 kN gives five instances, not the universal claim; the claim rests on a calculation (a proof) plus a safety factor. Scientific laws have the same shape, which is why one reproducible counterexample outweighs any number of confirmations.

Computing. A foreign-key constraint, "every order belongs to some customer", is \(\forall o\, \exists c\, \text{owns}(c, o)\). The query that finds violations is its negation, \(\exists o\, \forall c\, \neg\text{owns}(c, o)\): an orphan order, written in SQL as NOT EXISTS.

21.4.7. Exercises

  1. Translate into symbols with domain the integers, and decide truth: (a) every integer has a negative; (b) some integer is its own square; (c) there is an integer that divides every integer.

  2. Negate, pushing the negation all the way in: \(\forall x\, \big(x > 0 \to \exists y\, (y^2 = x)\big)\).

  3. Write a Python expression for "some row in table has every entry zero", and for its negation.

Answers
  1. (a) \(\forall x\, \exists y\, (x + y = 0)\): true (take \(y = -x\)). (b) \(\exists x\, (x^2 = x)\): true (\(x = 0\) or \(x = 1\)). (c) \(\exists d\, \forall n\, (d \text{ divides } n)\): true (\(d = 1\), or \(d = -1\)).

  2. \(\exists x\, \big(x > 0 \wedge \forall y\, (y^2 \neq x)\big)\): "some positive number is not a perfect square". Steps: flip \(\forall\) to \(\exists\); negate the conditional \(\neg(p \to q) \equiv p \wedge \neg q\); flip \(\exists y\) to \(\forall y\); negate \(=\) to \(\neq\). (Over the integers this negation is true, e.g. \(x = 2\), so the original is false; over the reals the original is true.)

  3. any(all(v == 0 for v in row) for row in table). Negation: all(any(v != 0 for v in row) for row in table): every row has at least one nonzero entry.

US curriculum

Formal quantifier notation (\(\forall\), \(\exists\)) is generally not taught in US high schools, though the ideas appear informally in Algebra II and Precalculus ("for all real \(x\)"). It is core material in college Discrete Mathematics and in an introductory proofs course.

21.5. Sets

21.5.1. The problem

A marketing team wants "customers who bought in 2025 but not in 2026, excluding anyone who unsubscribed". A security team wants "every IP address that appears in both the firewall log and the threat list". A compiler wants "the variables that are live at the end of this block". Each of these is a question about collections: combining them, intersecting them, removing one from another. The language for collections is set theory, and it turns out to be logic wearing different clothes.

21.5.2. Intuition

A set is a bag of distinct things where order does not matter and duplicates do not count. The only question you can ask of a set is "is this thing in it?" Everything else, including union, intersection and complement, is defined by how it answers that question. And the answer to "is \(x\) in \(A \cup B\)?" is "is \(x\) in \(A\) or is \(x\) in \(B\)?" So each set operation is a logical connective applied to membership.

21.5.3. Vocabulary and notation

Set, element, membership

A set is a well-defined collection of distinct objects, called its elements or members. We write \(x \in A\) for "\(x\) is an element of \(A\)" and \(x \notin A\) for "\(x\) is not". Sets are written with braces: \(A = \{2, 3, 5, 7\}\). Order and repetition are irrelevant: \(\{1, 2, 2, 3\} = \{3, 2, 1\}\). Two sets are equal when they have exactly the same elements.

The cardinality \(|A|\) is the number of elements of a finite set: \(|\{2, 3, 5, 7\}| = 4\).

Standard number sets

\(\mathbb{N}\) natural numbers \(\{0, 1, 2, \ldots\}\) (some authors start at 1; computer scientists usually start at 0); \(\mathbb{Z}\) integers (from German Zahlen, "numbers"); \(\mathbb{Q}\) rationals (quotient); \(\mathbb{R}\) reals; \(\mathbb{C}\) complex numbers. See Chapter 1.

Set-builder notation

Set-builder notation describes a set by a rule rather than a list:

\[\{\, x \in \mathbb{Z} \mid x > 0 \text{ and } x \text{ is even} \,\} = \{2, 4, 6, \ldots\}\]

Read the bar \(\mid\) (sometimes a colon) as "such that". The left part says where elements come from and what shape they have; the right part is a predicate they must satisfy. You can also put an expression on the left: \(\{\, n^2 \mid n \in \mathbb{N} \,\} = \{0, 1, 4, 9, \ldots\}\). Python’s set comprehension {n*n for n in range(10)} is set-builder notation, almost symbol for symbol.

Empty set, subset, universal set
  • The empty set \(\varnothing\) (or \(\{\}\)) has no elements; \(|\varnothing| = 0\). There is only one empty set.

  • \(A\) is a subset of \(B\), written \(A \subseteq B\), if every element of \(A\) is also in \(B\): \(\forall x\, (x \in A \to x \in B)\). A proper subset, \(A \subsetneq B\) (often written \(A \subset B\)), is a subset that is not equal to \(B\).

  • \(A = B\) exactly when \(A \subseteq B\) and \(B \subseteq A\). This "two inclusions" approach is the standard way to prove two sets equal.

  • The universal set \(U\) is the set of everything under discussion in a given context (all customers, all integers).

Two consequences of the definition of subset surprise people. First, \(\varnothing \subseteq A\) for every set \(A\): the condition "every element of \(\varnothing\) is in \(A\)" is vacuously true. Second, \(\in\) and \(\subseteq\) are different relations: \(2 \in \{1, 2\}\) but \(2 \not\subseteq \{1, 2\}\) (2 is not a set of elements of \(\{1,2\}\)); \(\{2\} \subseteq \{1, 2\}\) but \(\{2\} \notin \{1, 2\}\).

21.5.4. Operations on sets

Union, intersection, difference, complement

For sets \(A, B\) inside a universal set \(U\):

Operation Definition Logic behind it

Union \(A \cup B\)

\(\{x \mid x \in A \vee x \in B\}\)

or

Intersection \(A \cap B\)

\(\{x \mid x \in A \wedge x \in B\}\)

and

Difference \(A \setminus B\) (also \(A - B\))

\(\{x \mid x \in A \wedge x \notin B\}\)

and not

Complement \(\overline{A}\) (also \(A^c\), \(A'\))

\(\{x \in U \mid x \notin A\}\)

not

Symmetric difference \(A \,\triangle\, B\)

\((A \setminus B) \cup (B \setminus A)\)

xor

Sets with no common element, \(A \cap B = \varnothing\), are disjoint.

A Venn diagram draws the universal set as a rectangle and each set as a region inside it. Shading shows the result of an operation.

Six Venn diagrams shading union intersection difference complement symmetric difference and complement of union
Figure 193. Venn diagrams of the main set operations

Because each operation is a connective in disguise, every law of logic gives a law of sets. Replace \(\vee\) by \(\cup\), \(\wedge\) by \(\cap\), \(\neg\) by complement, \(\mathbf{T}\) by \(U\) and \(\mathbf{F}\) by \(\varnothing\):

Law Logic Sets

Distributive

\(p \wedge (q \vee r) \equiv (p \wedge q) \vee (p \wedge r)\)

\(A \cap (B \cup C) = (A \cap B) \cup (A \cap C)\)

De Morgan

\(\neg(p \vee q) \equiv \neg p \wedge \neg q\)

\(\overline{A \cup B} = \overline{A} \cap \overline{B}\)

De Morgan

\(\neg(p \wedge q) \equiv \neg p \vee \neg q\)

\(\overline{A \cap B} = \overline{A} \cup \overline{B}\)

Complement

\(p \vee \neg p \equiv \mathbf{T}\)

\(A \cup \overline{A} = U\)

Absorption

\(p \wedge (p \vee q) \equiv p\)

\(A \cap (A \cup B) = A\)

Three Venn diagrams showing complement of A, complement of B, and their intersection equal to complement of the union
Figure 194. De Morgan for sets: outside both circles is the overlap of the two outsides

A Venn diagram is a good way to discover or check a set identity. To prove one, use the membership argument below, because diagrams with three or more sets get hard to draw correctly and easy to misread.

Example 112. Procedure — proving a set identity by element-chasing

Input: a claimed identity \(X = Y\) between two set expressions.

Steps:

  1. Take an arbitrary element \(x\) and write "\(x \in X\)".

  2. Unfold the definitions of the operations in \(X\), turning membership into a logical formula in statements like \(x \in A\).

  3. Transform that formula with the laws of logic until it is the unfolded form of \(x \in Y\).

  4. Fold it back up: "\(x \in Y\)". Since every step was an equivalence (\(\iff\)), you have shown \(x \in X \iff x \in Y\) for all \(x\), so \(X = Y\).

  5. If some step is only an implication, prove the two inclusions \(X \subseteq Y\) and \(Y \subseteq X\) separately.

Output: a proof of the identity, or a counterexample element found while trying.

21.5.5. Worked example: De Morgan for sets

Prove \(\overline{A \cup B} = \overline{A} \cap \overline{B}\). Try it: De Morgan on a Venn diagram.

\[\begin{aligned} x \in \overline{A \cup B} &\iff \neg\,(x \in A \cup B) &&\text{(definition of complement)}\\ &\iff \neg\,(x \in A \vee x \in B) &&\text{(definition of union)}\\ &\iff \neg(x \in A) \wedge \neg(x \in B) &&\text{(De Morgan for logic)}\\ &\iff x \in \overline{A} \wedge x \in \overline{B} &&\text{(definition of complement, twice)}\\ &\iff x \in \overline{A} \cap \overline{B} &&\text{(definition of intersection)} \end{aligned}\]

The set identity is literally the logic identity, applied to membership.

21.5.6. Counting: inclusion–exclusion

How many elements are in \(A \cup B\)? Adding \(|A| + |B|\) counts the overlap twice, so subtract it once: Try it: Inclusion–exclusion with counts.

\[|A \cup B| = |A| + |B| - |A \cap B|.\]

Example: 120 users enabled two-factor authentication, 85 use a passkey, and 40 do both. Then \(|A \cup B| = 120 + 85 - 40 = 165\) users have at least one of the two. This principle of inclusion–exclusion grows to three or more sets in Chapter 24, and becomes the addition rule for probabilities in Chapter 29.

21.5.7. Power set and Cartesian product

Two constructions build new, bigger sets from old ones.

Power set

The power set \(\mathcal{P}(A)\) is the set of all subsets of \(A\), including \(\varnothing\) and \(A\) itself. For \(A = \{a, b, c\}\):

\[\mathcal{P}(A) = \big\{\varnothing,\ \{a\},\ \{b\},\ \{c\},\ \{a,b\},\ \{a,c\},\ \{b,c\},\ \{a,b,c\}\big\}.\]

If \(|A| = n\) then \(|\mathcal{P}(A)| = 2^n\), because building a subset means making an independent in-or-out decision for each of the \(n\) elements. A subset of an \(n\)-element set is an \(n\)-bit string; that is why bitmasks represent sets.

Ordered pair and Cartesian product

An ordered pair \((a, b)\) is like a two-element set except that order matters: \((1, 2) \neq (2, 1)\), and \((a, b) = (c, d)\) exactly when \(a = c\) and \(b = d\). More generally an \(n\)-tuple is \((a_1, \ldots, a_n)\).

The Cartesian product of \(A\) and \(B\) is the set of all ordered pairs with first entry from \(A\) and second from \(B\):

\[A \times B = \{\, (a, b) \mid a \in A,\ b \in B \,\}, \qquad |A \times B| = |A| \cdot |B|.\]

\(\mathbb{R} \times \mathbb{R} = \mathbb{R}^2\) is the coordinate plane of Chapter 10, which is where the name (after Descartes) comes from.

Computer-science connection: SQL is set theory

A relational database table is (almost) a set of tuples, and SQL is (almost) set theory. "Almost" because SQL tables can contain duplicate rows unless you use DISTINCT or a key.

  • UNION, INTERSECT and EXCEPT are \(\cup\), \(\cap\) and \(\setminus\) on result sets (UNION ALL keeps duplicates).

  • A WHERE clause is a predicate; the result is the set-builder \(\{\, r \in T \mid P(r) \,\}\). Its AND, OR, NOT follow the laws of this chapter, so De Morgan can rewrite NOT (a = 1 OR b = 2) as a <> 1 AND b <> 2. (Careful: SQL has a third truth value, NULL/unknown, and with it NOT (x = 1) does not select rows where x is NULL.)

  • FROM emp, dept forms the Cartesian product \(\text{emp} \times \text{dept}\). An inner JOIN …​ ON is, by definition, that product filtered by a predicate. Real query planners never build the whole product, but that is the meaning.

Grid of 9 employee and department pairs with 3 matching pairs highlighted
Figure 195. A join is a Cartesian product followed by a filter

You can watch this happen with Python’s built-in sqlite3 module:

import sqlite3

db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE emp(name TEXT, dept_id INT);
CREATE TABLE dept(id INT, dname TEXT);
INSERT INTO emp VALUES ('Ann', 1), ('Bo', 3), ('Cy', 1);
INSERT INTO dept VALUES (1, 'Sales'), (2, 'Ops'), (3, 'R&D');
""")
print(len(db.execute("SELECT * FROM emp, dept").fetchall()))  # A x B
print(db.execute("""SELECT name, dname FROM emp, dept
                    WHERE emp.dept_id = dept.id""").fetchall())
print(db.execute("""SELECT name, dname FROM emp JOIN dept
                    ON emp.dept_id = dept.id""").fetchall())
9
[('Ann', 'Sales'), ('Bo', 'R&D'), ('Cy', 'Sales')]
[('Ann', 'Sales'), ('Bo', 'R&D'), ('Cy', 'Sales')]

A forgotten join condition gives you the full Cartesian product: 9 rows here, but \(10^6 \times 10^6 = 10^{12}\) rows for two million-row tables. That is the classic "why is this query taking forever" bug.

Computer-science connection: Python sets

Python’s set type implements finite sets directly, using a hash table so that membership x in s takes constant time on average (versus scanning a list).

A = {1, 2, 3, 4}
B = {3, 4, 5}
print(A | B)         # union
print(A & B)         # intersection
print(A - B)         # difference
print(A ^ B)         # symmetric difference
print(3 in A, 7 in A)
print({1, 2} <= A)   # subset test
evens = {n for n in range(10) if n % 2 == 0}   # set-builder notation
print(evens)
{1, 2, 3, 4, 5}
{3, 4}
{1, 2}
{1, 2, 5}
True False
True
{0, 2, 4, 6, 8}

The operator choices are not accidental: | is bitwise or, & bitwise and, ^ bitwise xor. If you represent a subset of \(\{0, \ldots, 63\}\) as a 64-bit integer whose bit \(i\) is 1 when \(i\) is in the set, then set union is bitwise or. Bitsets, Bloom filters, permission masks and chess engines' bitboards all use this. Note that elements must be hashable and immutable, which is why Python has frozenset for sets of sets, and why {} is an empty dict; the empty set is set().

Russell’s paradox

Can a set contain itself? Consider \(R = \{\, S \mid S \notin S \,\}\), the set of all sets that are not members of themselves. Is \(R \in R\)? If it is, then by its own rule it is not; if it is not, then by its rule it is. Bertrand Russell found this contradiction in 1901, in the foundations Gottlob Frege had just finished building. The fix, modern axiomatic set theory (ZFC), forbids building a set from an unrestricted predicate: set-builder notation must always draw from an existing set, \(\{\, x \in A \mid P(x) \,\}\). The same self-reference drives Turing’s proof that the halting problem is undecidable.

Interactive Lab: Venn region painter

A three-set Venn diagram has eight regions. Type a set expression such as \((A \setminus B) \cup \overline{C}\) and the lab shades the matching regions; click regions to paint a shape and the lab finds an expression for it. A second panel shows the same regions as rows of a three-variable truth table, making the sets-equal-logic correspondence concrete.

21.5.8. Where you’d use this

Everyday: surveys and inclusion–exclusion. Of 30 people at a meeting, 18 drink coffee, 15 drink tea and 7 drink both. Then \(18 + 15 - 7 = 26\) drink at least one, so \(30 - 26 = 4\) want neither: order 4 bottles of water, not 0 and not 30 minus 33. Try it: Coffee, tea, or neither.

Medicine: blood types are subsets. Red cells carry some subset of three antigens, A, B and the Rhesus antigen D. The eight blood types are exactly the \(2^3 = 8\) subsets: O negative is \(\varnothing\), AB positive is \(\{A, B, D\}\). A transfusion of red cells is compatible when the donor’s set is a subset of the recipient’s, because the recipient’s immune system attacks any antigen it lacks. So O negative (\(\varnothing \subseteq\) everything) is the universal donor and AB positive the universal recipient; an A-positive patient (\(\{A, D\}\)) can receive O−, O+, A− or A+, the four subsets of \(\{A, D\}\).

Computing: search and similarity. A search engine stores, for each word, the set of documents containing it (an inverted index). The query cat dog is the intersection of two such sets. Near-duplicate detection compares documents by the Jaccard similarity \(|A \cap B| / |A \cup B|\) of their word sets: "the cat sat on the mat" and "the cat lay on the mat" share 4 of 6 distinct words, a similarity of \(4/6 \approx 0.67\).

21.5.9. Exercises

  1. Let \(U = \{1, \ldots, 10\}\), \(A = \{1, 2, 3, 4, 5\}\), \(B = \{4, 5, 6, 7\}\). Find \(A \cup B\), \(A \cap B\), \(A \setminus B\), \(B \setminus A\), \(\overline{A}\), and \(\overline{A \cup B}\).

  2. True or false: (a) \(\varnothing \in \{1, 2\}\); (b) \(\varnothing \subseteq \{1, 2\}\); (c) \(\{1\} \in \mathcal{P}(\{1, 2\})\); (d) \(|\mathcal{P}(\mathcal{P}(\varnothing))| = 2\).

  3. Write \(\{1, 4, 9, 16, 25\}\) in set-builder notation.

  4. List \(\{0, 1\} \times \{a, b, c\}\). How many elements does \(\{0,1\}^{8} = \{0,1\} \times \cdots \times \{0,1\}\) (8 factors) have, and what familiar object is each element?

  5. Prove \(A \setminus B = A \cap \overline{B}\) by element-chasing.

  6. In a class of 30 students, 18 take Spanish, 14 take French, and 5 take neither. How many take both?

Answers
  1. \(A \cup B = \{1,2,3,4,5,6,7\}\); \(A \cap B = \{4, 5\}\); \(A \setminus B = \{1,2,3\}\); \(B \setminus A = \{6, 7\}\); \(\overline{A} = \{6,7,8,9,10\}\); \(\overline{A \cup B} = \{8, 9, 10\}\). (Check De Morgan: \(\overline{A} \cap \overline{B} = \{6,\ldots,10\} \cap \{1,2,3,8,9,10\} = \{8,9,10\}\).)

  2. (a) False: the elements are 1 and 2, and \(\varnothing\) is neither. (b) True: the empty set is a subset of every set. (c) True: \(\{1\} \subseteq \{1,2\}\). (d) True: \(\mathcal{P}(\varnothing) = \{\varnothing\}\) has 1 element, so its power set has \(2^1 = 2\) elements, namely \(\varnothing\) and \(\{\varnothing\}\).

  3. \(\{\, n^2 \mid n \in \mathbb{Z},\ 1 \le n \le 5 \,\}\).

  4. \(\{(0,a), (0,b), (0,c), (1,a), (1,b), (1,c)\}\), six pairs. \(\{0,1\}^8\) has \(2^8 = 256\) elements, each an 8-tuple of bits: a byte.

  5. \(x \in A \setminus B \iff x \in A \wedge x \notin B \iff x \in A \wedge x \in \overline{B} \iff x \in A \cap \overline{B}\).

  6. \(30 - 5 = 25\) take at least one. \(25 = 18 + 14 - |S \cap F|\), so \(|S \cap F| = 7\).

US curriculum

Set notation, Venn diagrams and union/intersection appear in middle-school and Algebra I (grades 7–9), and again in Algebra II or Statistics in the probability unit ("compound events"). Power sets, Cartesian products and element-chasing proofs belong to college Discrete Mathematics.

21.6. Relations

21.6.1. The problem

"Is less than", "divides", "has the same remainder when divided by 3", "is a prerequisite of", "follows on social media", "is in the same connected component as". Each of these takes two things and answers yes or no. Some behave very differently from others: "same remainder" sorts things into groups, "prerequisite of" puts them in an order, "follows" does neither. To reason about these patterns, and to know which algorithms apply, we need precise names for the properties a relation can have.

21.6.2. Intuition

A relation is just a set of pairs: the pairs that are related. Draw the elements as dots, and draw an arrow from \(a\) to \(b\) whenever \(a\) is related to \(b\). The properties are then visual: a loop at every dot (reflexive), every arrow paired with a reverse arrow (symmetric), every two-step path having a shortcut (transitive). That dots-and-arrows picture is a directed graph, the subject of Chapter 25.

Relation

A (binary) relation \(R\) from set \(A\) to set \(B\) is a subset of the Cartesian product: \(R \subseteq A \times B\). We write \(a \mathrel{R} b\) for \((a, b) \in R\). A relation on \(A\) is a relation from \(A\) to itself. Example on \(\{1,2,3\}\): "less than" is \(\{(1,2), (1,3), (2,3)\}\).

Properties of a relation on a set \(A\)
Property Definition Example

Reflexive

\(\forall a\; (a \mathrel{R} a)\)

\(\le\), \(=\), "divides"

Symmetric

\(\forall a, b\; (a \mathrel{R} b \to b \mathrel{R} a)\)

\(=\), "is a sibling of"

Antisymmetric

\(\forall a, b\; (a \mathrel{R} b \wedge b \mathrel{R} a \to a = b)\)

\(\le\), \(\subseteq\)

Transitive

\(\forall a, b, c\; (a \mathrel{R} b \wedge b \mathrel{R} c \to a \mathrel{R} c)\)

\(<\), \(\le\), "is an ancestor of"

Antisymmetric is not the opposite of symmetric: a relation can be both (equality) or neither ("follows").

21.6.3. Equivalence relations

Equivalence relation and equivalence class

An equivalence relation is a relation that is reflexive, symmetric and transitive. It behaves like "is the same as, in some respect". Common notation is \(a \sim b\).

The equivalence class of \(a\) is \(\lbrack a] = \{\, x \in A \mid x \sim a \,\}\), everything equivalent to \(a\). The classes form a partition of \(A\): they are nonempty, pairwise disjoint, and their union is all of \(A\). Every element lives in exactly one class.

The canonical example: on the integers, say \(a \sim b\) when \(a\) and \(b\) leave the same remainder on division by 3. Check the three properties: Try it: Same remainder mod 3.

  • Reflexive: \(a\) has the same remainder as itself.

  • Symmetric: if \(a\) has the same remainder as \(b\), then \(b\) has the same remainder as \(a\).

  • Transitive: if \(a\) matches \(b\) and \(b\) matches \(c\), all three share one remainder.

So the integers split into exactly three classes, \(\lbrack 0] = \{\ldots, -3, 0, 3, 6, \ldots\}\), \(\lbrack 1] = \{\ldots, -2, 1, 4, 7, \ldots\}\) and \(\lbrack 2] = \{\ldots, -1, 2, 5, 8, \ldots\}\). This is the "congruence modulo 3" that Chapter 23 builds a whole arithmetic on.

Six numbers in three colored pairs 0 and 3, 1 and 4, 2 and 5
Figure 196. Same remainder mod 3 on \(\{0, \ldots, 5\}\): three classes. Lines mean "related"; self-loops (reflexivity) are omitted.

Why does an equivalence relation always partition? Suppose two classes \(\lbrack a]\) and \(\lbrack b]\) share some element \(x\). Then \(x \sim a\) and \(x \sim b\); by symmetry \(a \sim x\), and by transitivity \(a \sim b\). Now any \(y \in [a]\) has \(y \sim a \sim b\), so \(y \in [b]\), and vice versa. So two classes either coincide or are disjoint. Reflexivity ensures every element is in at least one class (its own).

21.6.4. Partial orders

Partial order

A partial order is a relation that is reflexive, antisymmetric and transitive. It behaves like "comes before or equals". A set with a partial order is a poset. It is partial because two elements may be incomparable: neither \(a \preceq b\) nor \(b \preceq a\). If every pair is comparable it is a total (or linear) order, like \(\le\) on numbers.

"Divides" on \(\{1, 2, 3, 4, 6, 12\}\) is a partial order: every number divides itself; if \(a \mid b\) and \(b \mid a\) for positive integers then \(a = b\); and divisibility chains. But 4 and 6 are incomparable: neither divides the other. A Hasse diagram draws a poset economically: put larger elements higher, draw a line only for "immediate" relationships, and let reflexivity and transitivity be implied (12 is above 2 via 4 or 6, so no separate 2–12 line is drawn). Try it: Divisors of 12 as a Hasse diagram.

Hasse diagram 1 at bottom, 2 and 3 above, 4 and 6 above those, 12 at top
Figure 197. Hasse diagram of the divisors of 12 under "divides"

21.6.5. Worked example: classifying relations

Classify each relation on the given set. Try it: Classify “less than”.

(a) "\(a \le b\)" on the integers. Reflexive (\(a \le a\)). Not symmetric (\(1 \le 2\) but \(2 \not\le 1\)). Antisymmetric (\(a \le b\) and \(b \le a\) force \(a = b\)). Transitive. So: a partial order, in fact a total order.

(b) "\(a < b\)" on the integers. Not reflexive (\(1 \not< 1\)). Antisymmetric (vacuously: \(a < b \wedge b < a\) never happens). Transitive. Neither an equivalence relation nor a partial order; it is a strict order.

(c) "\(x\) and \(y\) are friends" on users. Symmetric, not reflexive by convention, and not transitive (your friend’s friend may be a stranger). Not an equivalence relation, which is why "friend groups" are not well-defined but "connected components" (friends-of-friends-of-…​ chains) are: taking the transitive closure repairs it.

The checks are mechanical enough to code. Note how each property is a quantified statement turned into all():

from itertools import product

def properties(S, R):
    """R is a set of pairs (a, b) meaning a R b, over the finite set S."""
    reflexive = all((a, a) in R for a in S)
    symmetric = all((b, a) in R for (a, b) in R)
    transitive = all((a, d) in R
                     for (a, b) in R for (c, d) in R if b == c)
    antisymmetric = all(a == b for (a, b) in R if (b, a) in R)
    return dict(reflexive=reflexive, symmetric=symmetric,
                transitive=transitive, antisymmetric=antisymmetric)

S = range(6)
same_mod3 = {(a, b) for a, b in product(S, S) if a % 3 == b % 3}
divides = {(a, b) for a, b in product(range(1, 13), repeat=2) if b % a == 0}
less_than = {(a, b) for a, b in product(S, S) if a < b}
print(properties(S, same_mod3))
print(properties(range(1, 13), divides))
print(properties(S, less_than))
{'reflexive': True, 'symmetric': True, 'transitive': True, 'antisymmetric': False}
{'reflexive': True, 'symmetric': False, 'transitive': True, 'antisymmetric': True}
{'reflexive': False, 'symmetric': False, 'transitive': True, 'antisymmetric': True}
Transitivity is the one people assume

Many natural relations fail transitivity, and code that assumes it breaks. "Approximately equal", abs(a - b) < 1e-9, is reflexive and symmetric but not transitive: \(0 \approx 0.6 \times 10^{-9}\) and \(0.6 \times 10^{-9} \approx 1.2 \times 10^{-9}\), but \(0 \not\approx 1.2 \times 10^{-9}\). Using it as an equality in a hash table or a deduplication pass gives results that depend on processing order. Likewise "is preferred to" in voting can be cyclic (Condorcet’s paradox), so it is not an order at all, and a comparator that is not transitive can make sort routines misbehave or throw (Java’s TimSort reports "Comparison method violates its general contract!").

Computer-science connection: equivalence relations and partial orders in code
  • The equals/hash contract. Java requires equals to be reflexive, symmetric and transitive: an equivalence relation. Hash tables bucket objects by equivalence class, and the rule "equal objects must have equal hashes" says the hash is constant on each class. Break symmetry (a subclass equals that compares extra fields) and HashSet gives inconsistent answers.

  • Union–find (disjoint-set) data structures maintain the partition of an equivalence relation as pairs are added, used in Kruskal’s algorithm, image segmentation and type unification.

  • Partial orders describe dependencies: build targets, package versions, course prerequisites, and "happened-before" between events in a distributed system (vector clocks). A topological sort (Chapter 25) extends a partial order to a total order, giving a valid build or execution sequence. Two incomparable tasks can run in parallel.

21.6.6. Functions as special relations

A function is a relation with one extra rule. A relation \(f \subseteq A \times B\) is a function from \(A\) to \(B\), written \(f : A \to B\), if every \(a \in A\) is related to exactly one \(b \in B\). We then write \(f(a) = b\) instead of \((a, b) \in f\). In quantifier language: \(\forall a \in A\; \exists!\, b \in B\; (a, b) \in f\).

The relation "\(x\) is a square root of \(y\)" on real numbers is not a function from \(y\) to \(x\) (4 has two square roots, \(-1\) has none); the relation \(\{(x, x^2)\}\) is a function. A Python dict is a finite function: each key maps to exactly one value. The full treatment of domain, codomain, range, injective, surjective and inverse functions is in Chapter 11.

Function (as a relation)

A function \(f : A \to B\) is a relation from \(A\) to \(B\) in which each element of the domain \(A\) is paired with exactly one element of the codomain \(B\). "Exactly one" has two halves: at least one (defined everywhere on \(A\)) and at most one (no input has two outputs).

Interactive Lab: relation property explorer

Draw arrows between up to eight dots. The lab lights up which of reflexive, symmetric, antisymmetric and transitive hold, and for any that fail it highlights a witness (a missing loop, an unreturned arrow, a two-step path with no shortcut). Buttons add the reflexive, symmetric or transitive closure; when the result is an equivalence relation the dots recolor by class, and when it is a partial order the lab redraws it as a Hasse diagram.

21.6.7. Where you’d use this

Everyday: getting dressed is a partial order. Underwear before trousers, trousers before shoes, socks before shoes; shirt and socks are incomparable, so either order is fine. Any morning routine that respects the order is a topological sort of it, and recipes ("make the sauce while the pasta boils") are the same structure with parallel steps.

Physics: temperature exists because of an equivalence relation. The zeroth law of thermodynamics says that if bodies \(A\) and \(B\) are each in thermal equilibrium with \(C\), they are in equilibrium with each other. Together with symmetry, that is transitivity, so "in thermal equilibrium with" is an equivalence relation. A thermometer works by naming the equivalence classes: a temperature is a label for a class.

Biology and chemistry: classification. "Is a subgroup of" in taxonomy (species, genus, family, …​) is a partial order whose Hasse diagram is a tree. "Has the same molecular formula" is an equivalence relation whose classes group isomers (ethanol and dimethyl ether are both \(\mathrm{C_2H_6O}\)).

Computing. Beyond the sidebar above: version constraints in a package manager compare versions by a total order, while "this commit is an ancestor of that one" in Git is a partial order (two branches' tips are incomparable until merged).

21.6.8. Exercises

  1. On the set of all people, is "has the same birthday as" an equivalence relation? How many equivalence classes are there?

  2. On \(\{1, 2, 3\}\), give a relation that is symmetric and transitive but not reflexive.

  3. Draw the Hasse diagram of \(\mathcal{P}(\{a, b\})\) ordered by \(\subseteq\). Which pairs are incomparable? Try it: Hasse diagram of P({a, b}).

  4. Which of these relations from \(\{1,2,3\}\) to \(\{x, y\}\) are functions? (a) \(\{(1,x), (2,x), (3,y)\}\); (b) \(\{(1,x), (1,y), (2,x), (3,x)\}\); (c) \(\{(1,x), (3,y)\}\).

Answers
  1. Yes: reflexive, symmetric and transitive. There are 366 classes, one per calendar day including February 29 (assuming each day has at least one person born on it, which for all humanity is certainly true).

  2. \(R = \{(1,1)\}\): symmetric and transitive (trivially), not reflexive because \((2,2) \notin R\). Another: \(R = \varnothing\). (This is why the "proof" that symmetry plus transitivity implies reflexivity, "\(a R b\) and \(b R a\) give \(a R a\)", fails: it assumes some \(b\) related to \(a\) exists.)

  3. \(\varnothing\) at the bottom; \(\{a\}\) and \(\{b\}\) in the middle; \(\{a, b\}\) at the top; lines from \(\varnothing\) to each singleton and from each singleton to \(\{a,b\}\). The only incomparable pair is \(\{a\}\) and \(\{b\}\). The diagram is a diamond.

  4. (a) Yes: each input has exactly one output. (b) No: 1 has two outputs. (c) No: 2 has no output (it is a partial function).

US curriculum

Relations appear in Algebra I as "a set of ordered pairs", with functions defined as relations where each input has one output (the "vertical line test", grade 8–9). Equivalence relations, partial orders and their properties are college Discrete Mathematics. Equivalence classes meet high-school students informally in modular ("clock") arithmetic.

21.7. Coda: satisfiability, the hardest easy question

Given a boolean formula, is there any assignment of T/F to its variables that makes it true? This is the Boolean satisfiability problem, SAT. It sounds like an exercise from earlier in the chapter, and for three variables it is: try all eight rows.

from itertools import product

# (x or y) and (not x or z) and (not y or not z) and (not z or x)
clauses = [[1, 2], [-1, 3], [-2, -3], [-3, 1]]   # DIMACS-style literals

def satisfies(assign, clauses):
    return all(any(assign[abs(l)] == (l > 0) for l in c) for c in clauses)

def brute_force_sat(clauses, n):
    for bits in product([False, True], repeat=n):
        assign = dict(zip(range(1, n + 1), bits))
        if satisfies(assign, clauses):
            return assign
    return None

print(brute_force_sat(clauses, 3))
{1: False, 2: True, 3: False}

The formula is written in conjunctive normal form (CNF): an "and" of clauses, each an "or" of literals (a variable or its negation). Literal 3 means \(z\); -3 means \(\neg z\). Any formula can be converted to CNF with the laws from Table 2. Check the answer: \(x = F, y = T, z = F\) gives \((F \vee T) \wedge (T \vee F) \wedge (F \vee T) \wedge (T \vee F) = T\). Try it: Convert a formula to CNF.

Computer-science connection: SAT solvers

Brute force needs \(2^n\) rows. SAT was the first problem proved NP-complete (Cook, 1971): if you could solve every SAT instance quickly, you could solve every problem whose solutions can be checked quickly, which would settle the P versus NP question. No one expects a guaranteed-fast algorithm.

And yet modern SAT solvers (MiniSat, CaDiCaL, Kissat) routinely solve industrial instances with millions of variables, using clever search (conflict-driven clause learning: when a partial assignment fails, learn a new clause that rules out the reason, and backtrack non-chronologically). They are used to verify chip designs, find bugs in software (bounded model checking), resolve package dependencies (some package managers encode "which versions can coexist" as SAT), schedule, and even settle open problems in mathematics. SMT solvers such as Z3 extend SAT with integers, arrays and bit-vectors, and power many program verifiers. The worst case is exponential; the typical case, it turns out, is often not.

A 200-terabyte proof

The Boolean Pythagorean triples problem asked: can you color every positive integer red or blue so that no Pythagorean triple \(a^2 + b^2 = c^2\) is all one color? In 2016 a SAT solver showed that you can do it up to 7,824 but not to 7,825. The proof certificate was about 200 terabytes, at the time the largest proof ever produced. Every step is a resolution of clauses of the kind in this chapter.

21.8. Interactive tools

Browser tools on the companion website that go with this chapter. Each example link in the chapter opens one of them with that example already set up.

21.9. Mastery check

  1. Build the truth table of \((p \to q) \wedge (q \to p)\) and identify the connective it equals.

  2. State the contrapositive and the converse of "if a function is differentiable, it is continuous". Which one is guaranteed true, given that the original is true?

  3. Simplify if not (x >= 0 and x < len(a)) or a[x] is None: so that every not is gone (change comparisons as needed).

  4. Negate \(\forall \varepsilon > 0\; \exists N\; \forall n > N\; (|a_n| < \varepsilon)\), pushing the negation all the way in.

  5. Let \(A = \{1, 2, 3\}\) and \(B = \{2, 3, 4\}\). Find \(A \,\triangle\, B\), \(|A \times B|\), and \(|\mathcal{P}(A \cap B)|\).

  6. Is "\(a - b\) is an even integer" an equivalence relation on \(\mathbb{Z}\)? If so, describe its classes.

  7. Explain in one sentence why all([]) is True in Python and any([]) is False.

Decision rule: All correct without hesitation → move on to Chapter 22, which puts this logic to work. Stuck on 1–3 → reread Section 21.2 and Section 21.3. Stuck on 4 or 7 → reread Section 21.4. Stuck on 5–6 → reread Section 21.5 and Section 21.6, then do their exercises.

Mastery check answers
  1. Rows TT, TF, FT, FF: \(p \to q\) is T, F, T, T; \(q \to p\) is T, T, F, T; their conjunction is T, F, F, T. That is the biconditional \(p \leftrightarrow q\).

  2. Contrapositive: "if a function is not continuous, it is not differentiable" (guaranteed true, equivalent to the original). Converse: "if a function is continuous, it is differentiable" (not guaranteed, and in fact false: \(|x|\) at 0, see Chapter 17).

  3. De Morgan: \(\neg(x \ge 0 \wedge x < n) \equiv x < 0 \vee x \ge n\). So: if x < 0 or x >= len(a) or a[x] is None:. Short-circuiting means a[x] is only evaluated once the index is known to be in range.

  4. \(\exists \varepsilon > 0\; \forall N\; \exists n > N\; (|a_n| \ge \varepsilon)\). Each quantifier flips, the domain conditions (\(\varepsilon > 0\), \(n > N\)) stay, and \(<\) becomes \(\ge\). In words: there is some tolerance that the sequence keeps violating, no matter how far out you go; i.e. \(a_n\) does not tend to 0 (see Chapter 16).

  5. \(A \,\triangle\, B = \{1, 4\}\); \(|A \times B| = 3 \cdot 3 = 9\); \(A \cap B = \{2, 3\}\), so \(|\mathcal{P}(A \cap B)| = 2^2 = 4\).

  6. Yes. Reflexive: \(a - a = 0\) is even. Symmetric: if \(a - b = 2k\) then \(b - a = 2(-k)\). Transitive: if \(a - b = 2k\) and \(b - c = 2m\) then \(a - c = 2(k + m)\). Two classes: the even integers and the odd integers.

  7. all of an empty collection is an "and" of no statements, vacuously true because there is no element to violate it; any is an "or" of no statements, false because there is no witness.

US curriculum

Overall, this chapter maps to the logic unit of high-school Geometry, the sets-and-probability material of Algebra I/II, AP Computer Science A (boolean logic), and the first third of a typical college Discrete Mathematics course (often CS 2xx or MATH 2xx), which many CS majors take in their first year.