How many bits does information really need? From surprise to Huffman codes, with proofs and runnable Python.
Surprise, entropy, joint and conditional entropy, mutual information.
Prefix codes, unique decodability, the Kraft–McMillan inequality.
The source coding theorem H ≤ L < H+1, the AEP and typical sets.
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.
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:
Key idea: information is about uncertainty, not meaning. The content of a message does not matter, only how likely it was.
| Year | Who | What |
|---|---|---|
| 1924 | Nyquist | Telegraph speed and signal levels |
| 1928 | Hartley | Information = log(number of choices) |
| 1948 | Shannon | Entropy, source & channel theorems; the word "bit" (from Tukey) |
| 1949 | Kraft | Kraft inequality for prefix codes |
| 1949 | Fano | Shannon–Fano code |
| 1951 | Shannon | "Prediction and Entropy of Printed English" |
| 1952 | Huffman | Optimal prefix codes (an MIT term paper) |
| 1956 | McMillan | Kraft holds for all UD codes |
| 1976 | Rissanen, Pasco | Arithmetic coding |
I(p) = −log2 p = log2(1/p) bits.
Why a log? We want three things:
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.
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):
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
| Source | H (bits) | Meaning |
|---|---|---|
| fair coin | 1 | one yes/no question |
| 90/10 coin | 0.469 | very predictable |
| fair die | log26 ≈ 2.585 | between 2 and 3 questions |
| certain | 0 | nothing to learn |
A weather station sends one of four reports. Find the entropy with pencil and paper.
| Report | p | 1/p | surprise −log2 p | p × surprise |
|---|---|---|---|---|
| sun | 1/2 | 2 | 1 bit | 0.5 |
| cloud | 1/4 | 4 | 2 bits | 0.5 |
| rain | 1/8 | 8 | 3 bits | 0.375 |
| snow | 1/8 | 8 | 3 bits | 0.375 |
| H = sum of the last column | 1.75 bits | |||
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 ½.
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.
With math.log (base e) the first source gives 1.213 nats, not bits. Multiply nats by 1.4427 to get bits.
Shannon asked: what properties should a measure of uncertainty H(p1,…,pn) have?
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. ∎
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 curve above is drawn from 101 real points of h2(p), computed in the test file.
h(p) = −p log2 p − (1−p) log2(1−p)
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
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.
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.
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.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.
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)
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).
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.
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
| Model | bits/char |
|---|---|
| uniform, 27 symbols | 4.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).
ℋ = 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.
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.
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.
prefix ⊂ UD ⊂ nonsingular ⊂ all codes
The average length is L(C) = ∑x p(x) · ℓ(x). Our goal: make L small while staying UD.
| x | Singular | Nonsingular, not UD | UD, not prefix | Prefix |
|---|---|---|---|---|
| 1 | 0 | 0 | 10 | 0 |
| 2 | 0 | 010 | 00 | 10 |
| 3 | 0 | 01 | 11 | 110 |
| 4 | 0 | 10 | 110 | 111 |
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.
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.
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
∑i D−ℓi ≤ 1.
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
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.
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.
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.)
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 → ∞.
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.
111) is left unused.The test file checks is_prefix_free on both outputs.
L = E[ℓ(X)] ≥ H(X),
and there is a prefix code withL < H(X) + 1.
Equality L = H holds iff every p(x) is a power of 2 (a dyadic source) and ℓ(x) = −log2 p(x).
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.
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.
The +1 hurts when H is small. Fix: code blocks of k symbols as one super-symbol.
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.
−(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)
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.
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
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.
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
L = 1.9 vs Shannon's 2.4 and H = 1.846. Runs in O(n log n).
Order p1 ≥ p2 ≥ … ≥ pm. Some optimal code has:
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.
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.
| Scheme | bits/char | vs ASCII |
|---|---|---|
| ASCII | 8 | 100% |
| fixed-length, 25 symbols | 5 | 62.5% |
| Huffman on letters | 3.998 | 50.0% |
| entropy H1 (the floor for letter codes) | 3.962 | 49.5% |
| with 1 letter of context | 2.814 | 35.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.
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.
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.
ZIP, gzip, PNG and HTTP compression. LZ77 finds repeats, then canonical Huffman codes the output. Only code lengths are stored in the header.
Lossy transforms (DCT, MDCT) make many small numbers. Huffman codes then store them. JPEG also allows arithmetic coding.
Headers use a fixed Huffman table built from real web traffic, so common letters in URLs and cookies get short codes.
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.
CABAC and similar context-adaptive arithmetic coders: the adaptive models Huffman cannot handle well.
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.
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.)
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.
math.log is base e. Mixing bases silently scales results by 1.4427.
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).
The per-letter H1 ignores context. Real data has memory, and the right target is the entropy rate, which is lower.
Counting: there are 2n strings of length n but only 2n−1 shorter strings. Any lossless compressor makes some inputs longer.
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.
By convention it is 0. But 0 * math.log2(0) raises an error. Skip terms with p = 0, as our H() does.
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.
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.
−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.
26 equally likely letters give log2 26 ≈ 4.70 bits, not 26.
Try each one before you look to the right.
| Idea | Formula / fact |
|---|---|
| Surprise | −log2 p |
| Entropy | H = E[−log2 p(X)], forced by Shannon's axioms |
| Bounds | 0 ≤ H ≤ log n (Jensen) |
| Chain rule | H(X,Y) = H(X) + H(Y|X) |
| Mutual info | I = H(X) + H(Y) − H(X,Y) ≥ 0 |
| Kraft–McMillan | ∑ 2−ℓi ≤ 1 for all UD codes |
| Source coding | H ≤ L < H + 1; blocks: +1/k |
| AEP | typical sequences: ≈ 2nH of them, each ≈ 2−nH |
| Huffman | optimal symbol code, merge two rarest |
Entropy is the price of information: the fewest bits per symbol that any lossless code can pay.
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.
| Term | Meaning |
|---|---|
| Surprise (self-information) | −log2 p: information in one outcome |
| Entropy H(X) | Average surprise; the fewest bits per symbol on average |
| Bit / nat | Unit 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 rate | Bits per symbol of a source with memory, as blocks grow |
| Redundancy | Gap between the raw bits used and the entropy |
| Term | Meaning |
|---|---|
| Uniquely decodable | Every coded string splits back into symbols in one way only |
| Prefix (instantaneous) code | No 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 set | Sequences with probability near 2−nH; they carry almost all the probability |
| AEP | Law of large numbers for −(1/n) log p(Xn): it tends to H |
| Huffman code | Optimal prefix code: merge the two rarest, repeat |
| Arithmetic coding / ANS | Codes a whole message at once, so it gets below whole bits per symbol |