Entropy & Shannon's Source Coding Theorem

How many bits does information really need? From surprise to Huffman codes, with proofs and runnable Python.

Measure

Surprise, entropy, joint and conditional entropy, mutual information.

Codes

Prefix codes, unique decodability, the Kraft–McMillan inequality.

Limits

The source coding theorem H ≤ L < H+1, the AEP and typical sets.

Build

Shannon, Fano and Huffman codes, measured against the entropy bound.

Use ← / → or space to move. Every code block was run with Python 3; every output shown is the real output.

Roadmap WHERE WE GO

  1. Shannon 1948: the birth of information theory
  2. Surprise: information as −log p
  3. Entropy: definition and Shannon's axioms
  4. The binary entropy curve
  5. Bounds 0 ≤ H ≤ log n via Jensen
  6. Joint, conditional entropy and the chain rule
  7. Mutual information
  8. How much information is in English?
  9. Kinds of codes: nonsingular, UD, prefix
  10. Sardinas–Patterson test
  11. Kraft–McMillan inequality and proof
  12. Source coding theorem and proof
  13. Block coding: approaching H
  14. AEP and typical sets
  15. Shannon–Fano and Huffman codes
  16. Why Huffman is optimal
  17. Limits of Huffman → arithmetic coding
  18. Real-world uses, pitfalls, summary

1948: A Mathematical Theory of Communication HISTORY

Claude Shannon, at Bell Labs, published a two-part paper in the Bell System Technical Journal (July and October 1948).

It asked one clean question: what is the least number of bits needed to send a message, with or without noise?

His answer split into two theorems:

  • Source coding (this deck): data can be squeezed down to its entropy H, and no further.
  • Channel coding (next deck): a noisy channel has a capacity C, and you can send below it with tiny error.

Key idea: information is about uncertainty, not meaning. The content of a message does not matter, only how likely it was.

YearWhoWhat
1924NyquistTelegraph speed and signal levels
1928HartleyInformation = log(number of choices)
1948ShannonEntropy, source & channel theorems; the word "bit" (from Tukey)
1949KraftKraft inequality for prefix codes
1949FanoShannon–Fano code
1951Shannon"Prediction and Entropy of Printed English"
1952HuffmanOptimal prefix codes (an MIT term paper)
1956McMillanKraft holds for all UD codes
1976Rissanen, PascoArithmetic coding

Surprise: information in one outcome INTUITION

Definition (self-information). An outcome of probability p carries

I(p) = −log2 p = log2(1/p)  bits.

Why a log? We want three things:

  • Certain events say nothing: I(1) = 0.
  • Rare events say more: I goes down as p goes up.
  • Independent events add: I(pq) = I(p) + I(q).

The only continuous functions that turn products into sums are logs. So I(p) = −K log p. Base 2 picks the unit: the bit.

Base e gives nats. 1 nat = 1/ln 2 ≈ 1.4427 bits.

import math

def surprise(p):
    return 0.0 - math.log2(p)

for p in [1, 0.5, 0.25, 1/6, 0.01]:
    print(f"p={p:<6.4g} surprise={surprise(p):.3f} bits")
p=1      surprise=0.000 bits
p=0.5    surprise=1.000 bits
p=0.25   surprise=2.000 bits
p=0.1667 surprise=2.585 bits
p=0.01   surprise=6.644 bits

A fair coin flip is 1 bit. Two flips are 2 bits. A 1-in-100 event is about 6.6 bits: like guessing 6 or 7 yes/no questions right.

0.0 - x avoids printing -0.000 when p = 1.

Entropy: the average surprise DEFINITION

Definition. For a random variable X with distribution p(x),

H(X) = −∑x p(x) log2 p(x) = E[ −log2 p(X) ]

with the convention 0 log 0 = 0 (since p log p → 0 as p → 0).

Three ways to read H(X):

  • The expected surprise of one draw.
  • The uncertainty before you look.
  • The fewest bits per symbol any lossless code can use on average (the theorem we will prove).

H depends only on the probabilities, not on the labels. Renaming outcomes does not change it.

def H(probs):
    """Shannon entropy in bits. Terms with p=0 contribute 0."""
    return 0.0 - sum(p * math.log2(p) for p in probs if p > 0)

print(f"{H([0.5, 0.5]):.4f}")        # fair coin
print(f"{H([0.9, 0.1]):.4f}")        # biased coin
print(f"{H([1/6] * 6):.4f}")         # fair die
print(f"{H([1.0]):.4f}")             # certain
1.0000
0.4690
2.5850
0.0000
SourceH (bits)Meaning
fair coin1one yes/no question
90/10 coin0.469very predictable
fair dielog26 ≈ 2.585between 2 and 3 questions
certain0nothing to learn

Worked example: entropy by hand STEP BY STEP

A weather station sends one of four reports. Find the entropy with pencil and paper.

Reportp1/psurprise −log2 pp × surprise
sun1/221 bit0.5
cloud1/442 bits0.5
rain1/883 bits0.375
snow1/883 bits0.375
H = sum of the last column1.75 bits
  1. Step 1. Surprise of each outcome: log2(1/p).
  2. Step 2. Weight each surprise by how often it happens.
  3. Step 3. Add. That average is H.

A code that hits H exactly

Give each report a codeword as long as its surprise: sun = 0, cloud = 10, rain = 110, snow = 111. Average length = ½·1 + ¼·2 + ⅛·3 + ⅛·3 = 1.75 bits. This works because every p is a power of ½.

Now a lopsided source

With p = (0.7, 0.1, 0.1, 0.1): H = −0.7 log2 0.7 − 3 · 0.1 log2 0.1 ≈ 0.360 + 0.997 = 1.357 bits. The best whole-bit code (lengths 1, 2, 3, 3) averages 1.5 bits. It is close to H, but it cannot reach it.

Common mistake: the unit

With math.log (base e) the first source gives 1.213 nats, not bits. Multiply nats by 1.4427 to get bits.

Why this formula? Shannon's uniqueness theorem THEOREM

Shannon asked: what properties should a measure of uncertainty H(p1,…,pn) have?

  1. Continuity: H is continuous in the pi.
  2. Monotone: for uniform choices, A(n) = H(1/n,…,1/n) grows with n.
  3. Grouping: if a choice is split into two steps, H is the first step's H plus the weighted H of the second step.
Theorem (Shannon 1948, App. 2). The only H meeting 1–3 is

H = −K ∑ pi log pi,   K > 0.

Proof sketch. Grouping on uniform choices gives A(sm) = m·A(s). With monotonicity this forces A(n) = K log n. For rational pi = ni/N, split a uniform choice of N into groups of size ni: K log N = H(p) + ∑ pi K log ni. Solve for H(p). Continuity extends to all real p. ∎

1/21/2 A 1/21/2 B (1/4) C (1/4) H(1/2,1/4,1/4) = H(1/2,1/2) + ½·H(1/2,1/2)
p = [0.5, 0.25, 0.25]
lhs = H(p)
rhs = H([0.5, 0.5]) + 0.5 * H([0.5, 0.5])
print(f"{lhs:.4f} {rhs:.4f}")
1.5000 1.5000

Other axiom sets (Khinchin 1957, Faddeev 1956) give the same answer. Entropy is not a guess; it is forced.

The binary entropy function CURVE

00.51 10.5 max 1 bit at p = 1/2 h(0.11) ≈ 0.5 p h(p)

The curve above is drawn from 101 real points of h2(p), computed in the test file.

Binary entropy. For a coin with P(1) = p:

h(p) = −p log2 p − (1−p) log2(1−p)

  • Symmetric: h(p) = h(1−p).
  • Concave, with h''(p) = −1/(p(1−p) ln 2) < 0.
  • Steep at the ends: h'(p) = log2((1−p)/p) → ∞ as p → 0.
def h2(p):
    return H([p, 1 - p])

for p in [0.0, 0.1, 0.11, 0.25, 0.5]:
    print(f"h({p}) = {h2(p):.4f}")
h(0.0) = 0.0000
h(0.1) = 0.4690
h(0.11) = 0.4999
h(0.25) = 0.8113
h(0.5) = 1.0000

Bounds: 0 ≤ H(X) ≤ log n PROOF

Theorem. If X takes n values, then

0 ≤ H(X) ≤ log2 n.

H = 0 iff X is constant. H = log n iff X is uniform.

Lower bound. Each 0 ≤ p ≤ 1, so log(1/p) ≥ 0. Every term p log(1/p) is ≥ 0. The sum is 0 only if every term is 0, so each p is 0 or 1.

Jensen's inequality. If f is concave, then E[f(Z)] ≤ f(E[Z]). Equality iff Z is constant (for strictly concave f).

Upper bound. Let Z = 1/p(X). Since log is concave:

H(X) = E[ log 1/p(X) ]
    ≤ log E[ 1/p(X) ]
    = log ∑x p(x) · 1/p(x)
    = log n.  ∎

Equality needs 1/p(x) constant, so p(x) = 1/n for all x.

Reading: uniform is the most uncertain. A fixed-length code with ⌈log2 n⌉ bits is optimal only when the source is (close to) uniform. Any skew is room to compress.

Same proof in other words: log n − H(X) = D(p ‖ u) ≥ 0, the KL divergence from uniform.

Joint and conditional entropy, chain rule DEFINITIONS

Joint entropy. H(X,Y) = −∑x,y p(x,y) log p(x,y). It is just the entropy of the pair.
Conditional entropy.

H(Y|X) = ∑x p(x) H(Y | X=x) = −∑x,y p(x,y) log p(y|x)

The average uncertainty left in Y once you know X.
Chain rule. H(X,Y) = H(X) + H(Y|X).
More generally H(X1,…,Xn) = ∑i H(Xi | X1,…,Xi−1).

Proof. log p(x,y) = log p(x) + log p(y|x). Take −E[·] of both sides. ∎

Conditioning reduces entropy: H(Y|X) ≤ H(Y), with equality iff independent. (On average! A single H(Y|X=x) can be bigger.)

# joint distribution of (Weather, Umbrella)
P = {("sun", "no"): 0.45, ("sun", "yes"): 0.05,
     ("rain", "no"): 0.10, ("rain", "yes"): 0.40}
def marginal(P, i):
    m = Counter()
    for k, v in P.items():
        m[k[i]] += v
    return m
HXY = H(P.values())
HX = H(marginal(P, 0).values())
HY = H(marginal(P, 1).values())
HY_given_X = HXY - HX            # chain rule
I = HX + HY - HXY
print(f"H(X,Y)={HXY:.4f} H(X)={HX:.4f} H(Y)={HY:.4f}")
print(f"H(Y|X)={HY_given_X:.4f} I(X;Y)={I:.4f}")
H(X,Y)=1.5955 H(X)=1.0000 H(Y)=0.9928
H(Y|X)=0.5955 I(X;Y)=0.3973

The test file also computes H(Y|X) directly from the definition and checks it equals HXY - HX.

Mutual information SHARED BITS

Definition.

I(X;Y) = ∑x,y p(x,y) log p(x,y)⁄p(x)p(y)

= H(X) − H(X|Y) = H(Y) − H(Y|X)

= H(X) + H(Y) − H(X,Y)

  • How much knowing Y cuts your doubt about X.
  • Symmetric: I(X;Y) = I(Y;X).
  • I(X;Y) = D(p(x,y) ‖ p(x)p(y)) ≥ 0, and 0 iff independent.
  • I(X;X) = H(X): entropy is self-information.

In the weather example: seeing the umbrella tells you 0.397 bits of the 1 bit of weather doubt.

Mutual information is the star of the next deck: channel capacity is max I(X;Y).

H(X,Y) = 1.596 H(X|Y) 0.603 I(X;Y) 0.397 H(Y|X) 0.596 H(X) = 1.000 H(Y) = 0.993 X = weather, Y = umbrella

The Venn picture is exact for two variables. With three or more, the middle region I(X;Y;Z) can be negative, so treat the picture with care.

How much information is in English? MEASURED

Model English as letters a–z plus space: 27 symbols. A uniform guess would need log2 27 ≈ 4.755 bits per character.

Real letters are not uniform. Below, TEXT holds the opening paragraph of Dickens' A Tale of Two Cities (in the test file).

letters = [c for c in TEXT.lower() if c.isalpha() or c == " "]
text27 = "".join(letters)
counts = Counter(text27)
n = len(text27)
H1 = H(c / n for c in counts.values())
print(f"chars={n} symbols={len(counts)}")
print(f"H1 = {H1:.3f} bits/char   (log2 27 = {math.log2(27):.3f})")
print("top:", " ".join(f"{'_' if c == ' ' else c}:{k/n:.3f}"
                       for c, k in counts.most_common(6)))
chars=907 symbols=25
H1 = 3.962 bits/char   (log2 27 = 4.755)
top: _:0.191 e:0.121 t:0.080 i:0.065 o:0.064 a:0.063

Using context lowers it more. The entropy of the next letter given the last one:

big = Counter(zip(text27, text27[1:]))
nb = sum(big.values())
H2joint = H(v / nb for v in big.values())
first = Counter(a for a, _ in zip(text27, text27[1:]))
Hfirst = H(v / nb for v in first.values())
print(f"H(X2|X1) = {H2joint - Hfirst:.3f} bits/char")
H(X2|X1) = 2.814 bits/char
Modelbits/char
uniform, 27 symbols4.75
letter frequencies (this text)3.96
1 letter of context (this text)2.81
Shannon 1951, human guessing≈ 0.6 – 1.3

This paragraph lacks z and x, so only 25 symbols appear. Small samples also make the bigram number too low (see Pitfalls).

Entropy rate and redundancy CONTEXT

Entropy rate of a process X1, X2, …:

ℋ = limn→∞ H(X1,…,Xn) / n = lim H(Xn | X1,…,Xn−1)

(both limits exist and agree for stationary processes).

Each extra letter of context can only lower H(Xn | past), because conditioning reduces entropy. The chain rule then says the long-run bits per letter is this limit.

Redundancy = 1 − ℋ / log2|Σ|. For English, with ℋ ≈ 1 bit, about 75–80% of the letters are "predictable".

Shannon's guessing game (1951)

A person guesses the next letter of a text, again and again, until right. The count of guesses per letter is recorded. From those counts Shannon bounded the entropy of English at about 0.6 to 1.3 bits per letter.

Why it matters

Redundancy is why you can read "Th qck brwn fx" and why compressors work. It is also why typos rarely stop you: redundancy is a built-in error-correcting code.

Modern language models are entropy estimators. A model's cross-entropy loss in bits per character is an upper bound on the true entropy rate.

Kinds of codes DEFINITIONS

Code. A map C : 𝒳 → {0,1}*. Its extension C* encodes a string by joining codewords: C*(x1x2…) = C(x1)C(x2)…
  • Nonsingular: C is one-to-one.
  • Uniquely decodable (UD): C* is one-to-one. Every bit string splits in at most one way.
  • Prefix (instantaneous): no codeword is a prefix of another. You can decode each symbol the moment its last bit arrives.

prefix ⊂ UD ⊂ nonsingular ⊂ all codes

The average length is L(C) = ∑x p(x) · ℓ(x). Our goal: make L small while staying UD.

xSingularNonsingular, not UDUD, not prefixPrefix
100100
200100010
300111110
4010110111

Column 2: 010 could be "2", "1 4" or "3 1". Column 3 is UD, but after reading 11 you must look ahead to see if a 0 follows. Column 4 decodes at once.

nonsingular uniquely decodable prefix (instantaneous)

Testing unique decodability ALGORITHM

Prefix-freeness is easy to check. UD is harder: a code can fail only for long strings. The Sardinas–Patterson test (1953) decides it in finite time.

  1. Let S1 be the "dangling suffixes": w such that a w = b for codewords a ≠ b.
  2. Build Si+1 from suffixes between Si and C, in both directions.
  3. If some Si contains a codeword: not UD.
  4. If the sets start repeating or go empty: UD.

Only finitely many suffixes exist, so the loop must stop.

Why it works: a dangling suffix that is itself a codeword marks the point where two different parsings of the same bits line up again.

def uniquely_decodable(C):
    """Sardinas-Patterson test."""
    C = set(C)
    def dangling(A, B):
        return {b[len(a):] for a in A for b in B if b != a and b.startswith(a)}
    S = dangling(C, C)
    seen = set()
    while S:
        if S & C:
            return False
        key = frozenset(S)
        if key in seen:
            return True
        seen.add(key)
        S = dangling(S, C) | dangling(C, S)
    return True
print(uniquely_decodable(["0", "10", "110", "111"]))   # prefix
print(uniquely_decodable(["0", "01", "011", "0111"]))  # UD, not prefix
print(uniquely_decodable(["0", "01", "10"]))           # "010" is ambiguous
True
True
False

The Kraft–McMillan inequality THEOREM

Theorem (Kraft 1949, McMillan 1956). For a D-ary code with lengths ℓ1,…,ℓm:
  1. If the code is uniquely decodable, then

    ∑i D−ℓi ≤ 1.

  2. If the lengths satisfy this, a prefix code with those lengths exists.

So UD codes buy you nothing over prefix codes: any lengths a UD code can reach, a prefix code can reach too. From here on we only need prefix codes.

Think of 2−ℓ as a budget. Short codewords are costly. The total budget is 1.

def kraft(lengths, D=2):
    return sum(D ** -l for l in lengths)
print(kraft([1, 2, 3, 3]))      # complete prefix code
print(kraft([1, 2, 2, 3]))      # impossible
print(kraft([2, 2, 3]))         # room to spare
1.0
1.125
0.625
01 01 01 a = 0 budget 1/2 b = 10 1/4 c = 110 d = 111 1/8 + 1/8 dashed: subtrees used up by a leaf

A prefix code is the set of leaves of a D-ary tree. A codeword ends its branch, so no other codeword can live under it.

Proof of Kraft–McMillan PROOF

Prefix codes: count leaves

Let ℓmax be the longest length. In the full tree of depth ℓmax there are Dℓmax leaves.

A codeword at depth ℓi owns the Dℓmax−ℓi leaves under it. Prefix-free means these sets do not overlap. So

∑i Dℓmax−ℓi ≤ Dℓmax  ⇒  ∑i D−ℓi ≤ 1.

Converse: build it

Sort lengths ℓ1 ≤ ℓ2 ≤ …. Take the first free node at depth ℓ1, then at ℓ2, and so on. Each choice uses up a D−ℓ share of the leaves. Since the total is at most 1, a free node is always left. (Code on the next slide.)

UD codes: McMillan's trick

Let S = ∑x D−ℓ(x). Raise it to the power k:

Sk = ∑x1…xk D−(ℓ(x1)+…+ℓ(xk)) = ∑m=1kℓmax Am D−m

Here Am counts the k-symbol strings whose code has m bits. UD means those codes are all different strings of length m. So Am ≤ Dm, and

Sk ≤ ∑m=1kℓmax 1 = k ℓmax.

If S > 1, then Sk grows exponentially but kℓmax only linearly. That fails for large k. So S ≤ 1. ∎

Neat move: UD is a claim about all strings, so look at long strings and let k → ∞.

From lengths to a prefix code CONSTRUCTION

def code_from_lengths(lengths):
    """Canonical prefix code: assign codewords in order of length."""
    assert kraft(lengths) <= 1
    code, words, prev = 0, [], None
    for l in sorted(lengths):
        if prev is not None:
            code = (code + 1) << (l - prev)
        words.append(format(code, f"0{l}b"))
        prev = l
    return words
print(code_from_lengths([1, 2, 3, 3]))
print(code_from_lengths([2, 2, 3, 3, 3]))

def is_prefix_free(words):
    return not any(a != b and b.startswith(a) for a in words for b in words)
['0', '10', '110', '111']
['00', '01', '100', '101', '110']

This is the canonical code used by DEFLATE (ZIP, gzip, PNG). The step (code + 1) << (l - prev) moves to the next free node, then walks down to the new depth.

  • The encoder only has to send the lengths, not the codewords. The decoder rebuilds the same code.
  • In the second example Kraft sum is 2·¼ + 3·⅛ = ⅞ < 1, so one leaf (111) is left unused.
  • Unused leaves mean wasted budget. An optimal code has Kraft sum exactly 1 (a complete tree).

The test file checks is_prefix_free on both outputs.

Source coding theorem: the lower bound THEOREM

Theorem (Shannon 1948). Let X have entropy H(X). Every uniquely decodable binary code has

L = E[ℓ(X)] ≥ H(X),

and there is a prefix code with

L < H(X) + 1.

Equality L = H holds iff every p(x) is a power of 2 (a dyadic source) and ℓ(x) = −log2 p(x).

Gibbs' inequality. For distributions p, r: D(p‖r) = ∑ pi log(pi/ri) ≥ 0, equality iff p = r. (Jensen on −log.)

Proof of L ≥ H

Let c = ∑i 2−ℓi. By McMillan, c ≤ 1. Define ri = 2−ℓi/c, a distribution.

L − H = ∑ piℓi + ∑ pi log pi
    = ∑ pi log( pi / 2−ℓi )
    = ∑ pi log( pi / ri ) − log c
    = D(p ‖ r) + log(1/c)  ≥ 0 + 0.  ∎

The code's lengths define a distribution r. The extra bits you pay are exactly the KL divergence between the true p and the one your code "believes", plus the waste from an incomplete tree.

Source coding theorem: the upper bound PROOF

Shannon code: ℓi = ⌈log2(1/pi)⌉

Kraft holds: ℓi ≥ log(1/pi), so 2−ℓi ≤ pi. Summing, ∑ 2−ℓi ≤ ∑ pi = 1. So a prefix code with these lengths exists.

Short enough: ℓi < log(1/pi) + 1. Multiply by pi and sum:

L = ∑ piℓi < ∑ pi log(1/pi) + 1 = H + 1.  ∎

Rounding up each length costs less than 1 bit per symbol. That +1 is the price of using a whole number of bits.

def shannon_lengths(p):
    return [math.ceil(-math.log2(x)) for x in p]
p = [0.4, 0.3, 0.2, 0.1]
l = shannon_lengths(p)
L = sum(pi * li for pi, li in zip(p, l))
print(l, f"L={L:.3f} H={H(p):.3f} kraft={kraft(l):.4f}")
[2, 2, 3, 4] L=2.400 H=1.846 kraft=0.6875

The bound holds: 1.846 ≤ 2.400 < 2.846. But the Kraft sum is only 0.6875. Almost a third of the budget is wasted.

Shannon codes are within 1 bit of H but not optimal. Huffman will get L = 1.9 on this same source.

The Shannon code is still the heart of the proof. It is also exactly what arithmetic coding does, but for a whole message at once, so the +1 is paid once, not per symbol.

Block coding: approaching H THEOREM

The +1 hurts when H is small. Fix: code blocks of k symbols as one super-symbol.

Corollary. For i.i.d. X1…Xk, H(X1…Xk) = kH(X). Apply the theorem to the block:

kH ≤ Lk < kH + 1  ⇒  H ≤ Lk/k < H + 1/k.

So bits per symbol → H as k → ∞.

For a stationary (not i.i.d.) source the same argument gives Lk/k → ℋ, the entropy rate.

Cost: the alphabet grows as |𝒳|k. At k=8 a binary source already needs a 256-leaf tree. This is why arithmetic coding wins in practice.

def block_rate(p1, k):
    probs = {}
    for bits_ in itertools.product("01", repeat=k):
        pr = 1
        for b in bits_:
            pr *= p1 if b == "1" else 1 - p1
        probs["".join(bits_)] = pr
    c = huffman(probs)
    return sum(probs[s] * len(c[s]) for s in probs) / k
p1 = 0.1
print(f"H = {h2(p1):.4f} bits/symbol")
for k in [1, 2, 3, 4, 6, 8]:
    print(f"k={k}: {block_rate(p1, k):.4f} bits/symbol")
H = 0.4690 bits/symbol
k=1: 1.0000 bits/symbol
k=2: 0.6450 bits/symbol
k=3: 0.5327 bits/symbol
k=4: 0.4926 bits/symbol
k=6: 0.4702 bits/symbol
k=8: 0.4758 bits/symbol

Note k=8 is slightly worse than k=6. The bound H + 1/k is a guarantee, not a promise of steady progress.

The Asymptotic Equipartition Property THEOREM

AEP. If X1, X2, … are i.i.d. with entropy H, then

−(1/n) log2 p(X1,…,Xn) → H   in probability.

Proof. By independence, −(1/n) log p(Xn) = (1/n) ∑i (−log p(Xi)). This is an average of i.i.d. terms with mean E[−log p(X)] = H. The weak law of large numbers finishes it. ∎

In words: almost every long sequence you will actually see has probability about 2−nH. All "likely" sequences are about equally likely. That is the "equipartition".

It is the information-theory version of the law of large numbers. A random 1000-flip sequence of a 20% coin has about 200 ones, so its probability is about 0.22000.8800 = 2−1000·h(0.2).

random.seed(7)
p1 = 0.2
Hs = h2(p1)
for n_ in [10, 100, 1000, 10000]:
    x = [1 if random.random() < p1 else 0 for _ in range(n_)]
    k = sum(x)
    logp = k * math.log2(p1) + (n_ - k) * math.log2(1 - p1)
    print(f"n={n_:>5}: -1/n log2 p(x) = {-logp / n_:.4f}   (H = {Hs:.4f})")
n=   10: -1/n log2 p(x) = 1.1219   (H = 0.7219)
n=  100: -1/n log2 p(x) = 0.7619   (H = 0.7219)
n= 1000: -1/n log2 p(x) = 0.7539   (H = 0.7219)
n=10000: -1/n log2 p(x) = 0.7175   (H = 0.7219)
H = 0.722 n=10100 100010000

Typical sets: where the probability lives THEOREM

Typical set. Aε(n) = all xn with 2−n(H+ε) ≤ p(xn) ≤ 2−n(H−ε).
Properties (for n large enough):
  1. P(Aε(n)) > 1 − ε (from the AEP).
  2. |Aε(n)| ≤ 2n(H+ε): since 1 ≥ ∑A p ≥ |A| 2−n(H+ε).
  3. |Aε(n)| ≥ (1−ε) 2n(H−ε): same idea with 1.

A second proof of the source coding theorem

Index the typical set with n(H+ε)+1 bits, flag bit 0. Send anything else raw with flag 1. The average is n(H + ε') bits. So H bits per symbol suffice.

def typical_stats(n_, p1, eps):
    Hs = h2(p1)
    prob = 0.0
    count = 0
    for k in range(n_ + 1):
        rate = -(k * math.log2(p1) + (n_ - k) * math.log2(1 - p1)) / n_
        if abs(rate - Hs) <= eps:
            count += math.comb(n_, k)
            logterm = (math.lgamma(n_ + 1) - math.lgamma(k + 1) - math.lgamma(n_ - k + 1)
                       + k * math.log(p1) + (n_ - k) * math.log(1 - p1))
            prob += math.exp(logterm)
    return prob, math.log2(count) / n_
for n_ in [100, 500, 1000, 2000]:
    prob, lg = typical_stats(n_, 0.2, 0.05)
    print(f"n={n_:>4}: P(typical)={prob:.3f}  log2|A|/n={lg:.3f}  (H={h2(0.2):.3f}, all=1.000)")
n= 100: P(typical)=0.468  log2|A|/n=0.731  (H=0.722, all=1.000)
n= 500: P(typical)=0.838  log2|A|/n=0.759  (H=0.722, all=1.000)
n=1000: P(typical)=0.952  log2|A|/n=0.765  (H=0.722, all=1.000)
n=2000: P(typical)=0.995  log2|A|/n=0.767  (H=0.722, all=1.000)

With ε = 0.05: the typical set soon holds 99.5% of the probability. Yet it has only 20.767n of the 2n sequences: at n=2000 that is a fraction 2−466.

The single most likely sequence (all zeros) is not typical. Typical is not the same as most probable.

Shannon–Fano coding: top down ALGORITHM

Fano's method (1949): sort symbols by probability. Split the list into two parts with totals as equal as possible. Give the left part 0 and the right part 1. Recurse.

def fano(items):
    """items: list of (symbol, p) sorted by p desc -> {symbol: code}"""
    if len(items) == 1:
        return {items[0][0]: ""}
    total, run, best, cut = sum(p for _, p in items), 0, None, 1
    for i in range(1, len(items)):
        run += items[i - 1][1]
        diff = abs(total - 2 * run)
        if best is None or diff < best:
            best, cut = diff, i
    left = {s: "0" + c for s, c in fano(items[:cut]).items()}
    right = {s: "1" + c for s, c in fano(items[cut:]).items()}
    return {**left, **right}
q = [("a", .35), ("b", .17), ("c", .17), ("d", .16), ("e", .15)]
fc, hc = fano(q), huffman(dict(q))
Lf = sum(p * len(fc[s]) for s, p in q)
Lh = sum(p * len(hc[s]) for s, p in q)
print(f"Fano L={Lf:.2f}  Huffman L={Lh:.2f}  H={H(p for _, p in q):.3f}")
Fano L=2.31  Huffman L=2.30  H=2.233
  • Top-down splitting is greedy. A bad early split can never be undone.
  • Fano's code is always within 2 bits of H, but it is not always optimal.
  • Here it loses to Huffman by 0.01 bits per symbol: small, but real.

Fano set this as a class problem at MIT: find the best code. His student David Huffman found it by building the tree bottom up instead.

Huffman coding: bottom up ALGORITHM

def huffman(freqs):
    """freqs: {symbol: weight} -> {symbol: codeword}"""
    if len(freqs) == 1:
        return {s: "0" for s in freqs}
    tie = itertools.count()               # break ties without comparing dicts
    heap = [(w, next(tie), {s: ""}) for s, w in freqs.items()]
    heapq.heapify(heap)
    while len(heap) > 1:
        w1, _, a = heapq.heappop(heap)    # two lightest subtrees
        w2, _, b = heapq.heappop(heap)
        merged = {s: "0" + c for s, c in a.items()}
        merged.update({s: "1" + c for s, c in b.items()})
        heapq.heappush(heap, (w1 + w2, next(tie), merged))
    return heap[0][2]

p = {"a": 0.4, "b": 0.3, "c": 0.2, "d": 0.1}
code = huffman(p)
L = sum(p[s] * len(c) for s, c in code.items())
print(dict(sorted(code.items())), f"L={L:.2f} H={H(p.values()):.3f}")
{'a': '0', 'b': '10', 'c': '111', 'd': '110'} L=1.90 H=1.846
  1. Put each symbol in a min-heap by weight.
  2. Pop the two lightest trees. Join them under a new node whose weight is their sum.
  3. Repeat until one tree is left.
1.0 .6 .3 a .4 b .3 d .1 c .2 merges: d+c=.3, b+.3=.6, a+.6=1

L = 1.9 vs Shannon's 2.4 and H = 1.846. Runs in O(n log n).

Why Huffman is optimal PROOF SKETCH

Theorem (Huffman 1952). Among all prefix codes (and so, by McMillan, all UD symbol codes), Huffman's code has the smallest average length L.

Order p1 ≥ p2 ≥ … ≥ pm. Some optimal code has:

  1. Longer for rarer: pi > pj ⇒ ℓi ≤ ℓj. Else swap the two codewords: L changes by (pi−pj)(ℓj−ℓi) < 0, a contradiction.
  2. Full tree: every inner node has 2 children. Else move the lone child up and save a bit.
  3. Siblings at the bottom: the two rarest symbols, m and m−1, are siblings at max depth. (By 1 and 2, the deepest level has a sibling pair; swap the rarest two into it.)

Induction on m

Merge the two rarest symbols into one symbol of weight pm−1+pm. Call the new code problem P'. Any tree T of the form in step 3 comes from a tree T' for P' by splitting one leaf, and

L(T) = L(T') + pm−1 + pm.

The extra term is a constant. So minimizing L(T) is the same as minimizing L(T'). That is exactly Huffman's step: merge, then solve the smaller problem. The base case m = 2 is trivial. ∎

Greedy is safe here because of the exchange argument (step 3) plus optimal substructure (the merge identity). Fano's top-down split has no such guarantee.

Huffman is optimal among codes that give each symbol a whole number of bits. It is not optimal among all compressors.

Huffman on English, measured against H EXPERIMENT

code = huffman(counts)
bits = sum(len(code[c]) for c in text27)
print(f"Huffman: {bits / n:.3f} bits/char   H1: {H1:.3f}   "
      f"fixed: {math.ceil(math.log2(len(counts)))}   ASCII: 8")

# round trip decode
enc = "".join(code[c] for c in text27)
rev = {v: k for k, v in code.items()}
out, buf = [], ""
for b in enc:
    buf += b
    if buf in rev:
        out.append(rev[buf]); buf = ""
assert "".join(out) == text27
Huffman: 3.998 bits/char   H1: 3.962   fixed: 5   ASCII: 8

Decoding needs no separators: prefix-freeness means the first match in rev is the right one.

Schemebits/charvs ASCII
ASCII8100%
fixed-length, 25 symbols562.5%
Huffman on letters3.99850.0%
entropy H1 (the floor for letter codes)3.96249.5%
with 1 letter of context2.81435.2%

Huffman is only 0.036 bits above H1. When no symbol is very likely, the +1 worst case is far away.

To beat H1 you must use context: model p(xn | past). The source coding theorem then applies to the conditional distribution. This is how PPM, LZ77 and neural compressors win.

Limits of Huffman → arithmetic coding MOTIVATION

Every codeword is at least 1 bit. For a very skewed source that is a disaster:

p = {"x": 0.99, "y": 0.01}
c = huffman(p)
L = sum(p[s] * len(c[s]) for s in p)
print(f"H={H(p.values()):.4f}  Huffman L={L:.4f}  waste={L / H(p.values()):.1f}x")
for k in [1, 4, 8]:
    print(f"blocks of {k}: {block_rate(0.01, k):.4f} bits/symbol")
H=0.0808  Huffman L=1.0000  waste=12.4x
blocks of 1: 1.0000 bits/symbol
blocks of 4: 0.2727 bits/symbol
blocks of 8: 0.1572 bits/symbol

Blocks help, but even 256-symbol blocks are still 2× off. Adaptive models make it worse: the tree must be rebuilt when p changes.

Arithmetic coding (Rissanen, Pasco 1976)

Map the whole message to a sub-interval of [0,1). Each symbol shrinks the interval by its probability. The final width is p(xn). Send about ⌈log2 1/p(xn)⌉ + 1 bits to name a point in it.

x (0.99) 01 xx (0.9801): after "x", zoom in and split again
  • Total cost < nH + 2 bits: the overhead is paid once per message, not per symbol.
  • Models can change every symbol (adaptive, context-mixing, neural).
  • Modern cousin: ANS (asymmetric numeral systems, Jarek Duda), as fast as Huffman, used in zstd, Apple LZFSE and JPEG XL.

Where these ideas run today APPLICATIONS

DEFLATE

ZIP, gzip, PNG and HTTP compression. LZ77 finds repeats, then canonical Huffman codes the output. Only code lengths are stored in the header.

JPEG & MP3

Lossy transforms (DCT, MDCT) make many small numbers. Huffman codes then store them. JPEG also allows arithmetic coding.

HTTP/2 HPACK

Headers use a fixed Huffman table built from real web traffic, so common letters in URLs and cookies get short codes.

zstd, JPEG XL, Brotli

zstd uses Huffman for literals plus ANS / FSE for the rest. JPEG XL uses ANS. Brotli stays with Huffman, but picks among many tables by context.

Video (H.264/5, AV1)

CABAC and similar context-adaptive arithmetic coders: the adaptive models Huffman cannot handle well.

Machine learning

Cross-entropy loss = expected code length under the model. Lower loss = better compressor. Decision trees split on information gain = mutual information.

Morse code (1830s–40s) was an early variable-length code: E = ·, T = −, and Q = −−·−. It is not prefix-free; the gaps between letters act as a third symbol.

Pitfalls and misconceptions CAREFUL

"The entropy of this file is…"

Entropy belongs to a distribution, not a single string. The same file has different entropies under different models. (The length of the shortest program for a string is Kolmogorov complexity, a different idea.)

Plug-in estimates are biased low

Counting frequencies in a small sample underestimates H, badly for bigrams. Our 907-char sample has 192 distinct bigrams, and 56 of them appear only once.

Bits vs nats

math.log is base e. Mixing bases silently scales results by 1.4427.

"Huffman is optimal, so it reaches H"

It is optimal among symbol codes only. It can be up to 1 bit per symbol above H (12× off for a 99/1 source).

Non-i.i.d. data

The per-letter H1 ignores context. Real data has memory, and the right target is the entropy rate, which is lower.

You cannot compress everything

Counting: there are 2n strings of length n but only 2n−1 shorter strings. Any lossless compressor makes some inputs longer.

Common mistakes in exercises STUDENT TRAPS

Plain average instead of weighted

Average code length is L = ∑ pi ℓi. For lengths 1, 2, 3, 3 with p = (0.4, 0.3, 0.2, 0.1), L = 1.9, not (1+2+3+3)/4 = 2.25.

0 log 0 in code

By convention it is 0. But 0 * math.log2(0) raises an error. Skip terms with p = 0, as our H() does.

Kraft is about lengths, not a given code

Lengths 1, 2, 2 pass Kraft: ½ + ¼ + ¼ = 1. So some prefix code has them (0, 10, 11). The code 0, 01, 11 has the same lengths, but it is not prefix-free.

"Conditioning always lowers entropy"

Only on average: H(X|Y) ≤ H(X). One value y can raise it. Say Y = 0 (90%) forces X = 0, and Y = 1 makes X a fair coin. Then H(X) = 0.286, H(X|Y=1) = 1, and H(X|Y) = 0.1 bits.

Code lengths must be whole numbers

−log2 p is the ideal length, but you cannot send 1.32 bits for one symbol. The Shannon code rounds up. Rounding down can break Kraft.

Max entropy is log2 n, not n

26 equally likely letters give log2 26 ≈ 4.70 bits, not 26.

Check yourself EXERCISES

  1. What is the entropy of a fair 8-sided die?
  2. A fair coin has 1 bit of entropy. How many nats is that?
  3. Can a prefix code have codeword lengths 1, 2, 2, 3?
  4. Huffman gives lengths 1, 2, 3, 3 for p = (0.4, 0.3, 0.2, 0.1). Is the average length above, below or equal to H?
  5. X and Y are independent fair coins. What is I(X;Y)? What is H(X,Y)?
  6. Why can no lossless compressor shrink every 1000-bit file?

Try each one before you look to the right.

Answers

  1. log2 8 = 3 bits: three yes/no questions.
  2. ln 2 ≈ 0.693 nats.
  3. No. Kraft sum = ½ + ¼ + ¼ + ⅛ = 1.125 > 1.
  4. Above: L = 1.9 > H ≈ 1.846. It is within 1 bit, as the theorem says.
  5. I = 0, since independent means no shared information. H(X,Y) = 1 + 1 = 2 bits.
  6. There are 21000 such files but fewer than 21000 shorter outputs. Two files would share an output.

Summary RECAP

IdeaFormula / fact
Surprise−log2 p
EntropyH = E[−log2 p(X)], forced by Shannon's axioms
Bounds0 ≤ H ≤ log n (Jensen)
Chain ruleH(X,Y) = H(X) + H(Y|X)
Mutual infoI = H(X) + H(Y) − H(X,Y) ≥ 0
Kraft–McMillan∑ 2−ℓi ≤ 1 for all UD codes
Source codingH ≤ L < H + 1; blocks: +1/k
AEPtypical sequences: ≈ 2nH of them, each ≈ 2−nH
Huffmanoptimal symbol code, merge two rarest

Entropy is the price of information: the fewest bits per symbol that any lossless code can pay.

  • You cannot beat H on average (Gibbs + Kraft).
  • You can get within 1 bit per symbol (Shannon code), and within 1/k with blocks.
  • Huffman is the best whole-bit code; arithmetic coding and ANS close the last gap.
  • On real English: letters give 3.96 bits, context brings it near 1 bit.

Next: what if the channel is noisy? Shannon's second theorem says there is a speed limit, the capacity C, and below it errors can be made as rare as you like.

Glossary REFERENCE

TermMeaning
Surprise (self-information)−log2 p: information in one outcome
Entropy H(X)Average surprise; the fewest bits per symbol on average
Bit / natUnit with log base 2 / base e. 1 nat ≈ 1.4427 bits
Conditional entropy H(Y|X)Doubt left about Y once you know X, averaged over X
Mutual information I(X;Y)Bits that X tells you about Y; 0 iff independent
Entropy rateBits per symbol of a source with memory, as blocks grow
RedundancyGap between the raw bits used and the entropy
TermMeaning
Uniquely decodableEvery coded string splits back into symbols in one way only
Prefix (instantaneous) codeNo codeword starts another; decode as bits arrive
Kraft inequality∑ 2−ℓi ≤ 1: which lengths a prefix code can have
Average length L∑ pi ℓi, bits per symbol of a code
Typical setSequences with probability near 2−nH; they carry almost all the probability
AEPLaw of large numbers for −(1/n) log p(Xn): it tends to H
Huffman codeOptimal prefix code: merge the two rarest, repeat
Arithmetic coding / ANSCodes a whole message at once, so it gets below whole bits per symbol