Monocurl Essays
Series Download

The pigeonhole principle: obvious, and surprisingly strong

More objects than boxes forces two into one box. We prove the principle and its general form, then use it on problems where finding the boxes is the hard part.

20 min·High school and early college·

Ten pigeons fly one at a time into nine boxes. The first nine can each take an empty box. The tenth finds every box occupied, so whichever box it enters ends up holding two.
Monocurl source
let bx = |b| (b - 4) * 0.8
let PERM = [5, 1, 7, 3, 0, 8, 2, 6, 4]
let home = |i| [(i - 4.5) * 0.74 - 0.08, 1.2, 0]
let land = |i| [[bx(PERM[mod(i, 9)]) - 0.08, -1.2, 0], [bx(3) - 0.08, -0.6, 0]][i == 9]
let Bird = |pos, col| [
    fill{col} stroke{col, 1} Polygon([[pos[0] + 0.2, pos[1] + 0.085, 0], [pos[0] + 0.4, pos[1], 0], [pos[0] + 0.2, pos[1] - 0.085, 0]]),
    Disc(pos, 0.23, col, 0.45, 2.5),
    Pt([pos[0] + 0.085, pos[1] + 0.075, 0], col, 0.035)
]
let Flock = |s| map(range(0, 10), |i| {
    let u = clamp(0, (s - i * 0.8 - 0.5 * (i == 9)) / 1.5, 1)
    let e = u * u * (3 - 2 * u)
    return Bird(lerp(home(i), land(i), e) + [0, 0.45 * sin(PI * e), 0], [AZURE, EMBER][i == 9])
})
mesh boxes = [
    map(range(0, 9), |b| Box([bx(b), -0.75, 0], [0.74, 1.5], SLATE, 0.1, 2.5)),
    center{[0, 1.85, 0]} Cap("10 pigeons, 9 boxes", 0.85, INK)
]
mesh flock = Flock(s: 0)
mesh verdict = []

slide "fly"
    play Wait(0.6)
    flock.s = 9.6
    play Lerp(6.5, [&flock], linear)
    verdict = [
        Outline([[bx(3) - 0.4, -1.53, 0], [bx(3) + 0.4, -1.53, 0], [bx(3) + 0.4, 0.03, 0], [bx(3) - 0.4, 0.03, 0]], ROSE, 5),
        center{[0, -1.9, 0]} Cap("one box must hold two", 0.85, ROSE)
    ]
    play Fade(0.6, [&verdict])
    play Wait(2)

Uses the shared definitions for this lesson's figures.

Are there two people in London with exactly the same number of hairs on their heads? It sounds like a question that would need a census with tweezers. It can be settled from an armchair, and the answer is yes, with certainty.

A human head has around 100,000 hairs, and nobody has as many as a million. London has about nine million inhabitants. Give every possible hair count from 0 to 999,999 its own box, and ask each Londoner to stand in the box for their count. There are a million boxes and nine million people, so the people cannot all have a box to themselves. Two people in the same box have the same number of hairs.

The argument found no such pair and counted nobody’s hair. It used one idea, that more objects than boxes forces a box to be shared. This is the pigeonhole principle. Its statement is so plain that it is hard to believe it can prove anything interesting, and the purpose of what follows is to show that it can.

The principle and why it is true

Try it by hand first. Put 4 pigeons into 3 boxes while keeping every pigeon alone. The first three pigeons can take a box each, and then the fourth has nowhere to go. Said without reference to any order of arrival: if each of the 3 boxes held at most one pigeon, the boxes would hold at most 3 pigeons in total, and there are 4.

In the language of functions, with objects as inputs and boxes as outputs: if $A$ and $B$ are finite sets and $A$ has more elements than $B$, then every function $f$ from $A$ to $B$ sends two different elements of $A$ to the same element of $B$. The hair argument uses the function “number of hairs” from the set of Londoners to the set $\{0, 1, \dots, 999{,}999\}$.

Pause and try

The nine million Londoners and one million boxes are far from the borderline case of the theorem. Can you guarantee more than two people with the same hair count? How many?

Show answerHide answer

If every box held at most 8 people, the boxes would hold at most $8 \times 1{,}000{,}000 = 8$ million people, and there are 9 million. So some box holds at least 9 people: nine Londoners with exactly the same number of hairs.

More pigeons: the general form

The exercise used a stronger version of the principle. Here it is as a process. Take 22 objects in 5 boxes, distributed unevenly, and try to make the fullest box as empty as possible by moving objects out of it.

Twenty-two objects in five boxes. Objects are moved one at a time from the fullest box to the emptiest. The fullest box shrinks from 11 objects to 5 and then cannot shrink further, because the average, 22 / 5 = 4.4, is a floor that the fullest box can never go below.
Monocurl source
let START = [11, 6, 3, 2, 0]
let U = 0.3
let cx = |b| -3.0 + b * 1.0
let cell = |b, h| [cx(b), -1.72 + (h + 0.5) * U, 0]
let Item = |pos, col| center{pos} fill{tint(col, 0.35)} stroke{col, 2} Rect([0.62, U - 0.05])
let Stacks = |s| {
    var h = START
    let done = to_int(floor(s + 0.0001))
    var src = 0
    var dst = 0
    for (m in range(0, done + 1)) {
        src = 0
        dst = 0
        for (b in range(0, 5)) {
            if (h[b] > h[src]) {
                src = b
            }
            if (h[b] < h[dst]) {
                dst = b
            }
        }
        if (m < done and h[src] - h[dst] > 1) {
            h[src] = h[src] - 1
            h[dst] = h[dst] + 1
        }
    }
    let f = s - done
    let moving = (h[src] - h[dst] > 1) and (f > 0.001)
    var parts = []
    for (b in range(0, 5)) {
        for (k in range(0, h[b] - (moving and b == src))) {
            parts .= Item(cell(b, k), AZURE)
        }
    }
    if (moving) {
        let e = f * f * (3 - 2 * f)
        parts .= Item(lerp(cell(src, h[src] - 1), cell(dst, h[dst]), e) + [0, 0.5 * sin(PI * e), 0], EMBER)
    }
    parts .= center{[2.75, -0.55, 0]} scale{1.5} color{EMBER} Number(max_of(h) - (moving and f > 0.5), 0)
    return parts
}
mesh frame = [
    map(range(0, 5), |b| Path([[cx(b) - 0.4, 1.75, 0], [cx(b) - 0.4, -1.75, 0], [cx(b) + 0.4, -1.75, 0], [cx(b) + 0.4, 1.75, 0]], SLATE, 2.5)),
    center{[2.75, 1.5, 0]} Cap("22 objects", 0.8, INK),
    center{[2.75, 1.1, 0]} Cap("5 boxes", 0.8, INK),
    center{[2.75, 0.05, 0]} Note("fullest box holds", 0.74)
]
mesh stacks = Stacks(s: 0)
mesh avg = [
    stroke{LEAF, 3.5} Line([-3.55, -1.72 + 4.4 * U, 0], [1.55, -1.72 + 4.4 * U, 0]),
    center{[2.75, -1.2, 0]} Cap("average 4.4", 0.8, LEAF),
    center{[2.75, -1.6, 0]} Note("(green line)", 0.72)
]

slide "level"
    play Wait(0.8)
    stacks.s = 7
    play Lerp(7, [&stacks], linear)
    play Wait(2)

Uses the shared definitions for this lesson's figures.

The fullest box stops at 5. It cannot reach 4, because five boxes holding at most 4 each would hold at most 20 objects. The fullest box is always at least as full as the average box, and since it holds a whole number of objects, at least the average rounded up. For a real number $x$, write $\lceil x \rceil$ for the least integer that is greater than or equal to $x$, so $\lceil 4.4 \rceil = 5$ and $\lceil 9 \rceil = 9$.

When $N > n$ the quotient $N/n$ exceeds 1, so $\lceil N/n \rceil \ge 2$ and we recover the first theorem. For London, $\lceil 9{,}000{,}000 / 1{,}000{,}000 \rceil = 9$. The mirror image is also true, by the same proof with the inequalities reversed: some box contains at most $N/n$ rounded down.

Pause and try

How many people must be in a room to guarantee that at least three of them were born in the same month?

Show answerHide answer

The boxes are the 12 months. With 24 people it is possible that each month has exactly two, so 24 is not enough. With 25 people some month has at least $\lceil 25/12 \rceil = 3$. The answer is 25. In general, to force $k$ objects into one of $n$ boxes you need $n(k-1) + 1$ objects.

Choosing the holes: five points in a square

Here is a problem with no boxes in sight. Mark any five points in a square of side 1. I claim that some two of them are at most $\frac{\sqrt2}{2} \approx 0.707$ apart, wherever you put them.

The objects are surely the five points. For the principle to apply we need four boxes, and they must be chosen so that two points in the same box are forced to be close.

Pause and try

Find a way to divide the unit square into four regions so that any two points in the same region are at most $\frac{\sqrt2}{2}$ apart. Check your regions against the worst case: the two points of a region that are farthest from each other.

Show answerHide answer

Cut the square into four quarter squares of side $\frac12$. The farthest-apart points of a quarter are opposite corners, at distance $\sqrt{\left(\frac12\right)^2 + \left(\frac12\right)^2} = \frac{\sqrt2}{2}$.

  1. Five points in a square of side 1, spread out as well as I could manage.
  2. Cut the square into four quarter squares of side 1/2. These are the holes.
  3. Five points and four quarters: some quarter contains two of the points. Here it is the top left one.
  4. Two points in a quarter are at most its diagonal apart, which is √2/2 ≈ 0.707. This pair is closer than that.
Five points in a unit square. Cutting the square into four quarters gives four holes for five points, so two points share a quarter, and two points in one quarter are no farther apart than its diagonal.
Monocurl source
let S = 3.5
let C = [-1.75, 0, 0]
let P = [[0.15, 0.05], [0.9, 0.2], [0.9, 0.85], [0.08, 0.56], [0.4, 0.95]]
let up = |p| [C[0] + (p[0] - 0.5) * S, C[1] + (p[1] - 0.5) * S, 0]
mesh hole = []
mesh square = [
    Outline(sq(C, S), INK, 3.5),
    center{[C[0], -1.98, 0]} Note("side 1", 0.74)
]
mesh cuts = []
mesh link = []
mesh pts = map(P, |p| Pt(up(p), AZURE, 0.1))
mesh panel = []

slide "Five points in a square of side 1, spread out as well as I could manage."
    panel = [
        center{[2.2, 1.3, 0]} Cap("5 points", 0.85, AZURE)
    ]
    play Fade(0.6)

slide "Cut the square into four quarter squares of side 1/2. These are the holes."
    cuts = [
        Seg(up([0.5, 0]), up([0.5, 1]), SLATE, 2.5),
        Seg(up([0, 0.5]), up([1, 0.5]), SLATE, 2.5)
    ]
    panel .= center{[2.2, 0.75, 0]} Cap("4 quarters", 0.85, SLATE)
    play Fade(0.6)

slide "Five points and four quarters: some quarter contains two of the points. Here it is the top left one."
    hole = Region(sq(up([0.25, 0.75]), S / 2), tint(EMBER, 0.2))
    link = Seg(up(P[3]), up(P[4]), EMBER, 4)
    pts = map(range(0, 5), |i| Pt(up(P[i]), [AZURE, EMBER][i >= 3], 0.1))
    panel .= center{[2.2, 0.0, 0]} Cap("two share a quarter", 0.8, EMBER)
    play Fade(0.6)

slide "Two points in a quarter are at most its diagonal apart, which is √2/2 ≈ 0.707. This pair is closer than that."
    link = [
        stroke{LEAF, 3} dashed{} Line(up([0, 1]), up([0.5, 0.5])),
        Seg(up(P[3]), up(P[4]), EMBER, 4)
    ]
    panel = [
        center{[2.2, 1.3, 0]} Cap("5 points", 0.85, AZURE),
        center{[2.2, 0.75, 0]} Cap("4 quarters", 0.85, SLATE),
        center{[2.2, 0.0, 0]} Cap("two share a quarter", 0.8, EMBER),
        center{[2.2, -0.75, 0]} Math(str_join(["\text{diagonal } \frac{\sqrt{2}}{2} \approx ", to_string(sqrt(2) / 2, 3)]), 0.8, LEAF),
        center{[2.2, -1.45, 0]} Math(str_join(["\text{this pair: } ", to_string(dist(P[3], P[4]), 3)]), 0.8, EMBER)
    ]
    play Fade(0.6)

Uses the shared definitions for this lesson's figures.

Two questions test whether the result could be improved. Is 0.707 the best bound, and are five points needed? The right half of the next figure answers the first: the four corners and the center are five points with no pair closer than $\frac{\sqrt2}{2}$, so no smaller number can be guaranteed. The four corners alone are 1 apart or more, so with four points the claim is false.

The left half shows why the choice of holes mattered. Four vertical strips are also four regions, and the pigeonhole principle applies to them just as well: two of the five points share a strip. But that conclusion is worthless here, because a strip is tall.

Left: with four vertical strips as holes, two points must share a strip, but they can be nearly as far apart as the square is tall. Right: the four corners and the center are five points in which every pair is at least 0.707 apart, so the bound from the quarter squares cannot be improved.
Left: with four vertical strips as holes, two points must share a strip, but they can be nearly as far apart as the square is tall. Right: the four corners and the center are five points in which every pair is at least 0.707 apart, so the bound from the quarter squares cannot be improved.
Monocurl source
let S = 2.8
let CL = [-1.95, 0.25, 0]
let CR = [1.95, 0.25, 0]
let upc = |c, p| [c[0] + (p[0] - 0.5) * S, c[1] + (p[1] - 0.5) * S, 0]
let PL = [[0.12, 0.06], [0.2, 0.96], [0.4, 0.5], [0.62, 0.3], [0.88, 0.7]]
let PR = [[0, 0], [1, 0], [1, 1], [0, 1], [0.5, 0.5]]
mesh left = [
    Region([upc(CL, [0, 0]), upc(CL, [0.25, 0]), upc(CL, [0.25, 1]), upc(CL, [0, 1])], tint(ROSE, 0.12)),
    map([0.25, 0.5, 0.75], |x| Seg(upc(CL, [x, 0]), upc(CL, [x, 1]), SLATE, 2.5)),
    Outline(sq(CL, S), INK, 3.5),
    Seg(upc(CL, PL[0]), upc(CL, PL[1]), ROSE, 4),
    map(range(0, 5), |i| Pt(upc(CL, PL[i]), [AZURE, ROSE][i < 2], 0.1)),
    center{[CL[0], -1.55, 0]} Cap(str_join(["same strip, ", to_string(dist(PL[0], PL[1]), 2), " apart"]), 0.78, ROSE)
]
mesh right = [
    Outline(sq(CR, S), INK, 3.5),
    map(range(0, 4), |i| Seg(upc(CR, PR[i]), upc(CR, PR[4]), LEAF, 3.5)),
    map(PR, |p| Pt(upc(CR, p), AZURE, 0.1)),
    center{[CR[0], -1.55, 0]} Cap(str_join(["closest pairs: ", to_string(dist(PR[0], PR[4]), 3)]), 0.78, LEAF)
]

Uses the shared definitions for this lesson's figures.

Holes that are not places

In the square the holes were regions, which at least look like boxes. In number problems the holes are usually classes of numbers, and finding the right classes is the whole difficulty.

Choose six numbers from $1, 2, \dots, 10$. I claim that one of your numbers divides another. Five numbers are not enough to force this, since none of 6, 7, 8, 9, 10 divides another. To prove the claim we need five holes such that two numbers in the same hole always have one dividing the other. Grouping by size, or into odd and even, does nothing of the kind.

  1. None of 6, 7, 8, 9, 10 divides another. Can a sixth number from 1 to 10 be added without creating a pair where one divides the other?
  2. Sort the numbers into chains. Each chain starts at an odd number and keeps doubling. There are five chains, one for each odd number.
  3. The numbers 6 to 10 occupy all five chains. Any sixth number joins a chain that is already in use. Here 4 joins 8, and 4 divides 8.
The numbers 1 to 10 sorted into five chains. Each chain starts at an odd number and doubles. Within a chain every number divides the ones after it, so six numbers chosen from 1 to 10 must include two from the same chain, and one of those divides the other.
Monocurl source
let rowp = |n| [-3.15 + (n - 1) * 0.7, 0.75, 0]
let odd_part = |n| {
    var m = n
    while (mod(m, 2) == 0) {
        m = m // 2
    }
    return m
}
let chainp = |n| [-1.9 + log2(n / odd_part(n)) * 1.15, 1.5 - (odd_part(n) - 1) / 2 * 0.75, 0]
let Nums = |t| map(range(1, 11), |n| NumTile(lerp(rowp(n), chainp(n), t), n, AZURE))
let Mark = |pos, col| Outline(sq(pos, 0.66), col, 5)
mesh marks = []
mesh guide = []
mesh nums = Nums(t: 0)
mesh words = []

slide "None of 6, 7, 8, 9, 10 divides another. Can a sixth number from 1 to 10 be added without creating a pair where one divides the other?"
    marks = map(range(6, 11), |n| Mark(rowp(n), EMBER))
    words = [
        center{[0, -0.35, 0]} Cap("6, 7, 8, 9, 10: none divides another", 0.85, EMBER),
        center{[0, -0.95, 0]} Cap("is there room for a sixth?", 0.85, INK)
    ]
    play Fade(0.6)

slide "Sort the numbers into chains. Each chain starts at an odd number and keeps doubling. There are five chains, one for each odd number."
    marks = []
    words = []
    play Fade(0.3, [&marks, &words])
    nums.t = 1
    play Lerp(1.6, [&nums])
    guide = [
        map(range(0, 5), |r| center{[-3.05, 1.5 - r * 0.75, 0]} Note(str_join(["chain ", to_string(r + 1)]), 0.74)),
        map([1, 2, 4, 3, 5], |n| center{(chainp(n) + chainp(2 * n)) / 2 + [0, 0.02, 0]} Math("\times 2", 0.7, SLATE))
    ]
    words = [center{[2.55, 0.9, 0]} Cap("5 chains", 0.85, SLATE)]
    play Fade(0.5, [&guide, &words])

slide "The numbers 6 to 10 occupy all five chains. Any sixth number joins a chain that is already in use. Here 4 joins 8, and 4 divides 8."
    marks = [
        map(range(6, 11), |n| Mark(chainp(n), EMBER)),
        Mark(chainp(4), ROSE)
    ]
    words = [
        center{[2.55, 0.9, 0]} Cap("5 chains", 0.85, SLATE),
        center{[2.55, 0.35, 0]} Cap("6 numbers", 0.85, EMBER),
        center{[2.55, -0.4, 0]} Math("4 \text{ divides } 8", 0.9, ROSE)
    ]
    play Fade(0.6)

Uses the shared definitions for this lesson's figures.

The chains are found by pulling out factors of 2. Every positive integer can be written in exactly one way as $2^e \cdot m$ with $m$ odd and $e \ge 0$; call $m$ its odd part. The numbers $12 = 2^2 \cdot 3$ and $6 = 2 \cdot 3$ have the same odd part, 3. The general result is the following.

The number $n + 1$ cannot be lowered: the $n$ numbers $n + 1, n + 2, \dots, 2n$ contain no such pair, since twice the smallest of them already exceeds $2n$.

Worked examples

In the next three problems the objects, the holes or both are not what first comes to mind.

Ten numbers between 1 and 100. Their 1023 non-empty subsets have sums between 1 and 955, so two subsets must have the same sum. For these ten numbers a search finds 24 + 36 = 13 + 47.
Ten numbers between 1 and 100. Their 1023 non-empty subsets have sums between 1 and 955, so two subsets must have the same sum. For these ten numbers a search finds 24 + 36 = 13 + 47.
Monocurl source
let NUMS = [13, 24, 36, 47, 52, 65, 71, 88, 93, 98]
let bit = |m, i| mod(m // (2 ^ i), 2)
let total = |m| sum(map(range(0, 10), |i| bit(m, i) * NUMS[i]))
var seen = const_list(0, 956)
var first = 0
var second = 0
for (m in range(1, 1024)) {
    let s = to_int(total(m))
    if (second == 0) {
        if (seen[s] > 0) {
            first = seen[s]
            second = m
        } else {
            seen[s] = m
        }
    }
}
let MA = first
let MB = second
let inA = |i| bit(MA, i) == 1 and bit(MB, i) == 0
let inB = |i| bit(MB, i) == 1 and bit(MA, i) == 0
let listing = |f| str_join(map(filter(range(0, 10), f), |i| to_string(NUMS[i])), " + ")
let side_sum = |f| sum(map(filter(range(0, 10), f), |i| NUMS[i]))
mesh tiles = map(range(0, 10), |i| NumTile([-3.15 + i * 0.7, 1.55, 0], NUMS[i], [[SLATE, EMBER][inB(i)], AZURE][inA(i)], 0.6, 0.8, [0.1, 0.3][inA(i) or inB(i)]))
mesh sums = [
    center{[-1.7, 0.6, 0]} Math(str_join([listing(inA), " = ", to_string(side_sum(inA))]), 1.0, AZURE),
    center{[1.7, 0.6, 0]} Math(str_join([listing(inB), " = ", to_string(side_sum(inB))]), 1.0, EMBER)
]
mesh bars = [
    Box([-3.4 + 1023 * 0.003, -0.55, 0], [1023 * 0.006, 0.4], PLUM, 0.3, 2.5),
    center{[-3.4 + 1023 * 0.003, -0.55, 0]} Cap(str_join([to_string(2 ^ 10 - 1), " subsets (pigeons)"]), 0.78, PLUM),
    Box([-3.4 + 955 * 0.003, -1.3, 0], [955 * 0.006, 0.4], SLATE, 0.2, 2.5),
    center{[-3.4 + 955 * 0.003, -1.3, 0]} Cap(str_join([to_string(sum(range(91, 101))), " possible sums (holes)"]), 0.78, SLATE),
    Seg([-3.4 + 955 * 0.006, -0.25, 0], [-3.4 + 955 * 0.006, -1.9, 0], ROSE, 2.5)
]

Uses the shared definitions for this lesson's figures.

Seven integers and their eight running sums, starting from 0. The running sums are placed around a clock of the seven possible remainders on division by 7. Two of them, 9 and 23, land on the same remainder, so the block between them, 4 + 2 + 8, is a multiple of 7.
Seven integers and their eight running sums, starting from 0. The running sums are placed around a clock of the seven possible remainders on division by 7. Two of them, 9 and 23, land on the same remainder, so the block between them, 4 + 2 + 8, is a multiple of 7.
Monocurl source
let ROW = [3, 1, 5, 4, 2, 8, 3]
var acc = [0]
for (x in ROW) {
    acc .= acc[len(acc) - 1] + x
}
let SUMS = acc
let rem = |k| to_int(mod(SUMS[k], 7))
let rank = |k| len(filter(range(0, k), |j| rem(j) == rem(k)))
let twin = |k| len(filter(range(0, 8), |j| rem(j) == rem(k))) > 1
let CC = [-1.75, -0.15, 0]
let dir = |r| [sin(TAU * r / 7), cos(TAU * r / 7), 0]
mesh clock = [
    center{CC} fill{alpha{0} PAPER} stroke{SLATE, 2.5} Circle(1.0, 96),
    map(range(0, 7), |r| [
        Pt(CC + dir(r) * 1.0, SLATE, 0.06),
        center{CC + dir(r) * 0.68} Math(to_string(r), 0.78, SLATE)
    ]),
    center{CC} Note("rem.", 0.72)
]
mesh sums = map(range(0, 8), |k| NumTile(CC + dir(rem(k)) * (1.38 + rank(k) * 0.64), SUMS[k], [AZURE, EMBER][twin(k)], 0.46, 0.74, 0.25))
mesh row = [
    center{[2.1, 1.75, 0]} Note("the row", 0.74),
    map(range(0, 7), |i| NumTile([0.6 + i * 0.5, 1.25, 0], ROW[i], [SLATE, LEAF][i >= 3 and i <= 5], 0.44, 0.76, 0.25)),
    center{[2.1, 0.35, 0]} Note("running sums", 0.74),
    center{[2.1, -0.1, 0]} Math(str_join(map(SUMS, |s| to_string(s)), ",\, "), 0.76, INK),
    center{[2.1, -0.95, 0]} Math("23 - 9 = 4 + 2 + 8", 0.85, LEAF),
    center{[2.1, -1.45, 0]} Math("= 14 = 2 \cdot 7", 0.85, LEAF)
]

Uses the shared definitions for this lesson's figures.

Six people at a party, with lines joining friends, sorted into boxes by number of friends. Someone has five friends, so nobody can have zero, and the box for 0 is unusable. Six people in the five remaining boxes forces a shared box: two people here have three friends each.
Six people at a party, with lines joining friends, sorted into boxes by number of friends. Someone has five friends, so nobody can have zero, and the box for 0 is unusable. Six people in the five remaining boxes forces a shared box: two people here have three friends each.
Monocurl source
let EDGES = [[0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [1, 2], [1, 3], [1, 4], [2, 3]]
let deg = |v| len(filter(EDGES, |e| e[0] == v or e[1] == v))
let GC = [-1.9, 0.05, 0]
let gpos = |v| [GC[0] + 1.45 * sin(TAU * v / 6), GC[1] + 1.45 * cos(TAU * v / 6), 0]
let Person = |pos, v, r = 0.27, size = 0.8| [
    Disc(pos, r, [AZURE, EMBER][deg(v) == 3], 0.3, 2.5),
    center{pos} Cap(["A", "B", "C", "D", "E", "F"][v], size, [AZURE, EMBER][deg(v) == 3])
]
let slotx = |d| 0.55 + d * 0.57
let stack = |v| len(filter(range(0, v), |u| deg(u) == deg(v)))
mesh graph = [
    map(EDGES, |e| Seg(gpos(e[0]), gpos(e[1]), SLATE, 2.5)),
    map(range(0, 6), |v| Person(gpos(v), v))
]
mesh sorted = [
    map(range(0, 6), |d| [
        Path([[slotx(d) - 0.26, 0.75, 0], [slotx(d) - 0.26, -0.65, 0], [slotx(d) + 0.26, -0.65, 0], [slotx(d) + 0.26, 0.75, 0]], [SLATE, ROSE][d == 0], 2.5),
        center{[slotx(d), -0.95, 0]} Math(to_string(d), 0.8, [SLATE, ROSE][d == 0])
    ]),
    Seg([slotx(0) - 0.2, -0.5, 0], [slotx(0) + 0.2, 0.6, 0], ROSE, 3),
    Seg([slotx(0) - 0.2, 0.6, 0], [slotx(0) + 0.2, -0.5, 0], ROSE, 3),
    map(range(0, 6), |v| Person([slotx(deg(v)), -0.38 + stack(v) * 0.52, 0], v, 0.22, 0.72)),
    center{[slotx(2.5), 1.3, 0]} Note("boxes: number of friends", 0.74),
    center{[slotx(2.5), -1.6, 0]} Cap("6 people, 5 usable boxes", 0.78, INK)
]

Uses the shared definitions for this lesson's figures.

What the principle does not say

The conclusion of the pigeonhole principle is an existence statement, and that is all it is. It does not say which box is shared or which two objects share it. The hair argument proves that the nine Londoners exist and offers no help in finding them. The subset example guarantees two subsets with equal sums, and to exhibit them for a given set of ten numbers you still have to search.

The hypothesis is strict. With exactly $n$ objects in $n$ boxes nothing is forced, since each object can have its own box, and the party example showed how to deal with such a near miss. The principle is also a statement about finite sets. An infinite set of objects can be placed one per box into what looks like a smaller collection of boxes: send each positive integer $k$ to the even number $2k$, and all the positive integers have been stored in the even-numbered boxes alone, with no sharing.

Stepping back

Nothing here was hard to prove once the objects and the holes were named. The proofs were three or four lines long. What took thought was the naming: quarter squares in place of strips, odd parts, subsets in place of numbers, running sums in place of terms, and a count of friends with one value struck out. When a problem asks you to show that among enough things two must be alike in some way, try to describe “alike” as “in the same class”, and count the classes.

The principle belongs to a family of arguments that prove existence by counting or averaging, with no construction. Its relatives turn up in the birthday problem, where the question changes from “when is a shared box certain?” to “when is it likely?”, and in the probabilistic method, which proves that an object exists by showing that a random one has a positive chance of working.

Check yourself

A drawer contains 10 red socks, 10 blue socks and 10 black socks. You pull out socks in the dark. How many must you pull out to be certain of having two of the same color?

Problems

Problems 1 to 3 apply the principle directly. In problems 4 to 7 you must find the holes. Problem 8 is harder.

Problem 1

A drawer holds many red, blue and black socks. (a) How many socks must you take to be sure of three of the same color? (b) Among 100 people, at least how many must share a birth month?

Show solutionHide solution

(a) The boxes are the 3 colors. Six socks can be two of each color, so 6 is not enough. Seven socks give some color at least $\lceil 7/3 \rceil = 3$. The answer is 7, which is $n(k-1)+1$ with $n = 3$, $k = 3$. (b) With 12 months as boxes, some month contains at least $\lceil 100/12 \rceil = \lceil 8.33\ldots \rceil = 9$ people. Eight is not enough to account for everyone, since $12 \cdot 8 = 96 < 100$. Ten cannot be guaranteed, because four months with 9 people and eight months with 8 make $36 + 64 = 100$.

Problem 2

How many people must be gathered to be certain that two of them have the same birthday (day and month)? Why is the answer not 366?

Show solutionHide solution

The boxes are the possible birthdays, and counting 29 February there are 366 of them. With 366 people it is possible, if unlikely, that all birthdays differ. With 367 people two must coincide. So the answer is 367.

Problem 3

Show that among any 6 integers there are two whose difference is divisible by 5.

Show solutionHide solution

Let the holes be the five possible remainders on division by 5, namely 0, 1, 2, 3, 4, and put each integer in the hole of its remainder. Six integers in five holes gives two integers $a$ and $b$ with the same remainder $r$: $a = 5q + r$ and $b = 5q' + r$. Then $a - b = 5(q - q')$ is divisible by 5.

Problem 4

Five points are marked in an equilateral triangle of side 2 (the boundary is allowed). Show that two of them are at distance at most 1.

Show solutionHide solution

Join the midpoints of the three sides. This cuts the triangle into four equilateral triangles of side 1, three at the corners and one in the middle. Assign each point to a small triangle that contains it. Five points and four small triangles put two points in the same small triangle. Two points of an equilateral triangle of side 1 are at distance at most 1, because the farthest-apart points of a triangle are two of its vertices. (Reason: fix one point $P$. Along any line, the distance from $P$ decreases up to the foot of the perpendicular from $P$ and increases after it, so on a segment it is largest at one of the two ends. Every point of the triangle lies on a segment from a vertex to a point of the opposite side, and that point lies on a segment between two vertices. So the point of the triangle farthest from $P$ is a vertex, and the same argument with that vertex fixed moves $P$ to a vertex too.)

Problem 5

A lattice point is a point of the plane whose two coordinates are integers. Show that among any five lattice points there are two whose midpoint is also a lattice point.

Show solutionHide solution

The midpoint of $(x_1, y_1)$ and $(x_2, y_2)$ is $\left(\frac{x_1 + x_2}{2}, \frac{y_1 + y_2}{2}\right)$, which is a lattice point exactly when $x_1 + x_2$ and $y_1 + y_2$ are both even, that is, when $x_1$ and $x_2$ are both even or both odd, and likewise $y_1$ and $y_2$. So let the holes be the four parity patterns of a point: (even, even), (even, odd), (odd, even), (odd, odd). Five points in four holes give two points with the same pattern, and their midpoint is a lattice point. Four points do not suffice: $(0,0), (0,1), (1,0), (1,1)$ have all patterns different.

Problem 6

Show that among any $n + 1$ integers chosen from $\{1, 2, \dots, 2n\}$ there are two with no common factor greater than 1. Show that $n$ chosen integers are not enough.

Show solutionHide solution

Let the holes be the $n$ pairs $\{1, 2\}, \{3, 4\}, \dots, \{2n-1, 2n\}$. Each of the numbers lies in exactly one pair, so $n + 1$ chosen numbers include both members of some pair, say $k$ and $k + 1$. A common factor of $k$ and $k + 1$ divides their difference, which is 1, so their only common factor is 1. For the second part, choose the $n$ even numbers $2, 4, \dots, 2n$: every two of them have the common factor 2.

Problem 7

Explain why the decimal expansion of a fraction $\frac{p}{q}$, with $p$ and $q$ positive integers and $q \ge 2$, eventually repeats, and why the repeating block has at most $q - 1$ digits. Illustrate with $\frac17$.

Show solutionHide solution

Carry out the long division of $p$ by $q$. After the decimal point, each step takes the current remainder $r$, a number from $0$ to $q - 1$, appends a zero to form $10r$, writes the quotient digit of $10r$ divided by $q$, and keeps the new remainder. The next digit and the next remainder depend only on the current remainder. If a remainder is 0 the expansion terminates, which is a repeating block of zeros. Otherwise every remainder is one of the $q - 1$ values $1, \dots, q - 1$, so among the first $q$ remainders two are equal. From the second occurrence on, the division repeats exactly what it did after the first. The length of the repeating block is the gap between the two occurrences, at most $q - 1$. For $\frac17$ the remainders are $1, 3, 2, 6, 4, 5$ and then $1$ again, giving $\frac17 = 0.142857\,142857\ldots$ with a block of 6 digits, the maximum for $q = 7$.

Problem 8

Show that any list of 10 different real numbers contains four terms, not necessarily adjacent but in their original order, that are increasing, or four that are decreasing. (For instance $5, 9, 1, 7, 3, 8, 2, 10, 4, 6$ contains $5, 7, 8, 10$.)

Show solutionHide solution

Suppose, for contradiction, that some list $x_1, \dots, x_{10}$ has no increasing and no decreasing run of four terms. For each position $k$, let $u_k$ be the length of the longest increasing run that ends at $x_k$, and $d_k$ the length of the longest decreasing run that ends at $x_k$. By the supposition both are 1, 2 or 3, so the pair $(u_k, d_k)$ is one of $3 \times 3 = 9$ possibilities. These are the holes, and the 10 positions are the pigeons. So two positions $i < j$ have $(u_i, d_i) = (u_j, d_j)$. The numbers are different, so either $x_i < x_j$ or $x_i > x_j$. If $x_i < x_j$, take an increasing run of length $u_i$ ending at $x_i$ and append $x_j$: this is an increasing run of length $u_i + 1$ ending at $x_j$, so $u_j \ge u_i + 1$, contradicting $u_j = u_i$. If $x_i > x_j$, the same argument with decreasing runs gives $d_j \ge d_i + 1$, again a contradiction. So the supposition is false. The same proof shows that $n^2 + 1$ different numbers contain a run of length $n + 1$ in one direction or the other, a theorem of Erdős and Szekeres from 1935.

Shared definitions used by this lesson's figures
# house kit: prepended to every essay figure after the std imports

# palette. AZURE and EMBER are the two actors, LEAF marks an answer, ROSE a problem
let PAPER = [1, 1, 1, 1]
let INK = [0.08, 0.09, 0.11, 1]
let SLATE = [0.40, 0.44, 0.50, 1]
let MIST = [0.85, 0.86, 0.88, 1]
let AZURE = [0.13, 0.42, 0.68, 1]
let EMBER = [0.89, 0.42, 0.12, 1]
let LEAF = [0.13, 0.55, 0.36, 1]
let ROSE = [0.78, 0.22, 0.24, 1]
let PLUM = [0.47, 0.30, 0.66, 1]
let GOLD = [0.85, 0.62, 0.08, 1]

background = PAPER

# an opaque pastel of col; use for fills so overlaps do not darken
let tint = |col, amount = 0.18| [
    1 - (1 - col[0]) * amount,
    1 - (1 - col[1]) * amount,
    1 - (1 - col[2]) * amount,
    1
]

# text: Cap for labels, Note for quiet labels, Math for typeset maths
let Cap = |txt, size = 0.75, col = INK| color{col} Text(txt, size, "Charter")
let Note = |txt, size = 0.65| color{SLATE} Text(txt, size, "Charter")
let Math = |tex, size = 0.9, col = INK| color{col} Tex(tex, size)

# a paper rectangle to sit behind text that crosses lines
let Plate = |pos, size| center{pos} fill{PAPER} stroke{alpha{0} PAPER, 0} Rect(size)

# shapes with fill and stroke both set, so no stray black appears
let Pt = |pos, col = INK, r = 0.075| center{pos} fill{col} stroke{col, 1} Circle(r, 48)
let Ring = |pos, col = INK, r = 0.1, w = 3| center{pos} fill{PAPER} stroke{col, w} Circle(r, 48)
let Seg = |a, b, col = INK, w = 3| stroke{col, w} Line(a, b)
let Path = |pts, col = INK, w = 3| stroke{col, w} Polyline(pts)
let Region = |pts, col| fill{col} stroke{alpha{0} col, 0} Polygon(pts)
let Outline = |pts, col = INK, w = 3| fill{alpha{0} PAPER} stroke{col, w} Polygon(pts)
let Shape = |pts, col, amount = 0.18, w = 3| fill{tint(col, amount)} stroke{col, w} Polygon(pts)
let Disc = |pos, r, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Circle(r, 96)
let Box = |pos, size, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Rect(size)
# a line from a to b ending in a small solid arrowhead
let Tip = |a, b, col = INK, w = 3, head = 0.17| {
    let d = normalize([b[0] - a[0], b[1] - a[1], 0])
    let n = [-d[1], d[0], 0]
    let base = [b[0] - d[0] * head, b[1] - d[1] * head, 0]
    return [
        stroke{col, w} Line(a, base),
        fill{col} stroke{col, 1} Polygon([
            b,
            [base[0] + n[0] * head * 0.42, base[1] + n[1] * head * 0.42, 0],
            [base[0] - n[0] * head * 0.42, base[1] - n[1] * head * 0.42, 0]
        ])
    ]
}

# plots. a frame is [x0, x1, y0, y1, centre, width, height]: a data window and the scene box it fills
let Frame = |x0, x1, y0, y1, c = [0, 0, 0], w = 6.4, h = 3.6| [x0, x1, y0, y1, c, w, h]
let fx = |fr, x| fr[4][0] + (x - (fr[0] + fr[1]) / 2) * fr[5] / (fr[1] - fr[0])
let fy = |fr, y| fr[4][1] + (y - (fr[2] + fr[3]) / 2) * fr[6] / (fr[3] - fr[2])
let at = |fr, x, y| [fx(fr, x), fy(fr, y), 0]

# axes through the data origin (or the nearest edge of the window), with optional names
let Axes = |fr, x_name = "", y_name = "", col = SLATE| {
    let ox = clamp(fr[0], 0, fr[1])
    let oy = clamp(fr[2], 0, fr[3])
    var parts = [
        Tip(at(fr, fr[0], oy), [fx(fr, fr[1]) + 0.25, fy(fr, oy), 0], col, 2.5, 0.15),
        Tip(at(fr, ox, fr[2]), [fx(fr, ox), fy(fr, fr[3]) + 0.25, 0], col, 2.5, 0.15)
    ]
    if (x_name != "") {
        parts .= center{[fx(fr, fr[1]) + 0.5, fy(fr, oy), 0]} Math(x_name, 0.8, col)
    }
    if (y_name != "") {
        parts .= center{[fx(fr, ox), fy(fr, fr[3]) + 0.5, 0]} Math(y_name, 0.8, col)
    }
    return parts
}

# tick marks with numbers at the listed data values
let XTicks = |fr, xs, col = SLATE, places = 0| block {
    let oy = fy(fr, clamp(fr[2], 0, fr[3]))
    for (x in xs) {
        . Seg([fx(fr, x), oy - 0.07, 0], [fx(fr, x), oy + 0.07, 0], col, 2)
        . center{[fx(fr, x), oy - 0.33, 0]} Math(to_string(x, places), 0.72, col)
    }
}
let YTicks = |fr, ys, col = SLATE, places = 0| block {
    let ox = fx(fr, clamp(fr[0], 0, fr[1]))
    for (y in ys) {
        . Seg([ox - 0.07, fy(fr, y), 0], [ox + 0.07, fy(fr, y), 0], col, 2)
        . center{[ox - 0.36, fy(fr, y), 0]} Math(to_string(y, places), 0.72, col)
    }
}

# faint grid lines at the listed data values
let GridLines = |fr, xs, ys, col = MIST| block {
    for (x in xs) {
        . Seg(at(fr, x, fr[2]), at(fr, x, fr[3]), col, 1.5)
    }
    for (y in ys) {
        . Seg(at(fr, fr[0], y), at(fr, fr[1], y), col, 1.5)
    }
}

# the graph of f over [a, b], drawn in the frame. values are clamped to the window
let Graph = |fr, f, a, b, col = AZURE, w = 4, n = 160|
    stroke{col, w} Polyline(map(sample(a, b, n), |x| at(fr, x, clamp(fr[2], f(x), fr[3]))))

# the region between the graph of f and the x axis over [a, b]
let AreaUnder = |fr, f, a, b, col = AZURE, amount = 0.2, n = 80| {
    var pts = [at(fr, a, 0)]
    for (x in sample(a, b, n)) {
        pts .= at(fr, x, clamp(fr[2], f(x), fr[3]))
    }
    pts .= at(fr, b, 0)
    return fill{tint(col, amount)} stroke{alpha{0} col, 0} Polygon(pts)
}

# a labeled number tile
let NumTile = |pos, n, col = SLATE, w = 0.56, size = 0.8, amount = 0.15| [
    Box(pos, [w, w], col, amount, 2.5),
    center{pos} Math(to_string(n), size, col)
]
let sq = |c, s| [[c[0] - s / 2, c[1] - s / 2, 0], [c[0] + s / 2, c[1] - s / 2, 0], [c[0] + s / 2, c[1] + s / 2, 0], [c[0] - s / 2, c[1] + s / 2, 0]]
let dist = |p, q| sqrt((p[0] - q[0]) ^ 2 + (p[1] - q[1]) ^ 2)