A Programmer's Guide to Concrete Mathematics

A Programmer's Guide to Concrete Mathematics

Most programmers hit a wall at some point with mathematics. You are reading about an algorithm, the analysis section opens with a summation or a recurrence relation, and suddenly you are lost. Concrete Mathematics by Graham, Knuth, and Patashnik is the book that fixes this. It was written specifically to bridge the gap between programming and the mathematics used in algorithm analysis, and it does so with a directness that most textbooks lack.

This post gives you a practical introduction to two of its core topics — binomial coefficients and sums of powers — implemented in Python with worked examples at every step.


What Is Concrete Mathematics?

The name is a deliberate blend of continuous and discrete mathematics. While college calculus courses focus on continuous functions and smooth curves, Concrete Mathematics focuses on the discrete, integer-valued problems that actually arise when you analyze algorithms: how many steps does this loop take, how many ways can this set be arranged, how does this recurrence grow?

The main topics covered in the book are:

Topic Why it matters for programmers
Summations Counting the total work done by loops
Recurrence relations Analyzing recursive algorithms like merge sort
Binomial coefficients Counting combinations and subsets
Discrete probability Analyzing randomized algorithms
Generating functions Solving recurrences and counting problems

Binomial Coefficients

What They Are

The binomial coefficient \(\binom{n}{k}\) — read "n choose k" — counts the number of ways to choose \(k\) items from a set of \(n\) items, where the order of selection does not matter.

For example, \(\binom{5}{2} = 10\) means there are 10 different ways to pick 2 items from a set of 5. The formula is:

$$\binom{n}{k} = \frac{n!}{k!\,(n-k)!}$$

where \(n!\) means \(n \times (n-1) \times \cdots \times 2 \times 1\) — the factorial of \(n\). Let's verify the example: \(\binom{5}{2} = \frac{5!}{2!\,3!} = \frac{120}{2 \times 6} = 10\). ✓

Pascal's Triangle

Binomial coefficients form a pattern called Pascal's triangle, where each number is the sum of the two numbers directly above it:

$$\binom{n}{k} = \binom{n-1}{k-1} + \binom{n-1}{k}$$

This is called the recurrence relation for binomial coefficients. Written out as a triangle:

\(n=0\):1
\(n=1\):11
\(n=2\):121
\(n=3\):1331
\(n=4\):14641
\(n=5\):15101051

Each row \(n\) contains the values \(\binom{n}{0}, \binom{n}{1}, \ldots, \binom{n}{n}\). Pick any number in the interior of the triangle — say 10 in row 5 — and you can verify it equals the sum of the two numbers directly above it: \(4 + 6 = 10\). ✓

Computing Binomial Coefficients in Python

Python's standard library includes math.comb(n, k), which computes \(\binom{n}{k}\) directly and efficiently. We can wrap it in our own function:

from math import comb

def binomial(n: int, k: int) -> int:
    """Return the binomial coefficient C(n, k) — the number of ways
    to choose k items from n items without regard to order."""
    return comb(n, k)

Let's see it in action with a concrete example:

# How many ways can you choose 2 items from n items?
print(f"{'n':>4}  {'C(n,2)':>8}  {'Meaning'}")
print("-" * 45)
for n in range(2, 8):
    print(f"{n:>4}  {binomial(n, 2):>8}  ways to choose 2 from {n} items")
   n    C(n,2)  Meaning
---------------------------------------------
   2         1  ways to choose 2 from 2 items
   3         3  ways to choose 2 from 3 items
   4         6  ways to choose 2 from 4 items
   5        10  ways to choose 2 from 5 items
   6        15  ways to choose 2 from 6 items
   7        21  ways to choose 2 from 7 items

Notice that \(\binom{n}{2} = \frac{n(n-1)}{2}\) — the same formula as the sum of integers \(0 + 1 + \cdots + (n-1)\). This is not a coincidence; it is one of many connections between binomial coefficients and summations that Concrete Mathematics explores.

Verifying the Recurrence Relation

We can verify that the Pascal's triangle rule \(\binom{n}{k} = \binom{n-1}{k-1} + \binom{n-1}{k}\) holds in Python too:

for n, k in [(5, 2), (6, 3), (10, 4)]:
    left  = binomial(n-1, k-1)
    right = binomial(n-1, k)
    total = left + right
    direct = binomial(n, k)
    print(f"C({n},{k}) = C({n-1},{k-1}) + C({n-1},{k}) = {left} + {right} = {total},  direct = {direct},  match = {total == direct}")
C(5,2) = C(4,1) + C(4,2) = 4 + 6 = 10,  direct = 10,  match = True
C(6,3) = C(5,2) + C(5,3) = 10 + 10 = 20,  direct = 20,  match = True
C(10,4) = C(9,3) + C(9,4) = 84 + 126 = 210,  direct = 210,  match = True

Sums of Powers

What They Are

A sum of powers is what you get when you add up the same power of every integer from 1 to \(n\). The most common example is the sum of squares:

$$\sum_{k=1}^{n} k^2 = 1^2 + 2^2 + 3^2 + \cdots + n^2 = \frac{n(n+1)(2n+1)}{6}$$

The right-hand side is a closed form — a direct formula that gives the answer in a single calculation, without looping through every value. For \(n = 4\): \(\frac{4 \times 5 \times 9}{6} = 30\), and indeed \(1 + 4 + 9 + 16 = 30\). ✓

Computing It in Python

We can compute the sum of squares two ways — by brute force, and using the closed form — then verify they agree:

def sum_of_squares(n: int) -> int:
    """Brute-force sum of squares: 1^2 + 2^2 + ... + n^2."""
    return sum(k * k for k in range(1, n + 1))

def sum_of_squares_closed(n: int) -> int:
    """Closed form: n(n+1)(2n+1) / 6."""
    return n * (n + 1) * (2 * n + 1) // 6

k * k for k in range(1, n + 1) — a generator expression that produces \(1^2, 2^2, 3^2, \ldots, n^2\) one at a time. sum() adds them all up.

// 6 — integer division. Since \(n(n+1)(2n+1)\) is always exactly divisible by 6 (one of \(n\) or \(n+1\) is even, and among any three consecutive integers one is divisible by 3), this gives an exact integer result with no rounding.

print(f"{'n':>4}  {'brute force':>12}  {'closed form':>12}  {'match':>6}")
print("-" * 42)
for n in range(1, 11):
    bf = sum_of_squares(n)
    cf = sum_of_squares_closed(n)
    print(f"{n:>4}  {bf:>12}  {cf:>12}  {str(bf == cf):>6}")
   n   brute force   closed form   match
------------------------------------------
   1             1             1    True
   2             5             5    True
   3            14            14    True
   4            30            30    True
   5            55            55    True
   6            91            91    True
   7           140           140    True
   8           204           204    True
   9           285           285    True
  10           385           385    True

Every row matches. For large \(n\), the closed form is dramatically faster — the brute-force version loops through \(n\) additions, while the closed form does the same job in three multiplications regardless of how large \(n\) is.


How These Topics Connect

One thing that makes Concrete Mathematics distinctive is that it shows how these topics are not isolated — they are facets of the same underlying ideas. For example:

The sum \(\sum_{k=1}^{n} k = \frac{n(n+1)}{2}\) is the same as \(\binom{n+1}{2}\). In other words, the sum of the first \(n\) integers equals the number of ways to choose 2 items from \(n+1\) items. We can verify this:

for n in range(1, 8):
    sum_k = sum(k for k in range(1, n + 1))
    binom = binomial(n + 1, 2)
    print(f"n={n}: sum 1..n = {sum_k},  C({n+1},2) = {binom},  match = {sum_k == binom}")
n=1: sum 1..n = 1,  C(2,2) = 1,  match = True
n=2: sum 1..n = 3,  C(3,2) = 3,  match = True
n=3: sum 1..n = 6,  C(4,2) = 6,  match = True
n=4: sum 1..n = 10,  C(5,2) = 10,  match = True
n=5: sum 1..n = 15,  C(6,2) = 15,  match = True
n=6: sum 1..n = 21,  C(7,2) = 21,  match = True
n=7: sum 1..n = 28,  C(8,2) = 28,  match = True

This kind of connection — where a summation identity turns out to be a combinatorial identity in disguise — is exactly what Concrete Mathematics is about. Recognising these patterns is what allows a skilled programmer to replace an \(O(n)\) loop with an \(O(1)\) formula.


Where to Go Next

The two examples in this post barely scratch the surface. Concrete Mathematics goes on to cover recurrence relations (how to find closed forms for recursive algorithms), generating functions (a powerful tool for solving counting problems), and asymptotic analysis (how to compare the growth rates of different algorithms). Each chapter builds on the previous ones, and Python makes an excellent companion for checking your work at every step.

If you are building up your Python fundamentals alongside your mathematics, you might also find these articles useful:

Demystifying @classmethod in Python  ·  Choosing Between cls and self Parameters in Python Classes

Comments