Number Theory & Algebra contents
Gray Code
Order all n-bit numbers so that neighbours differ in exactly one bit; conversions in both directions and where it is used.
Read first: Bit Manipulation
A Gray code is a way to list all binary numbers of bits so that consecutive numbers (and, cyclically, the last and the first) differ in exactly one bit. It was invented for mechanical encoders: when a sensor moves from one position to the next, only one bit changes, so a misread can only be off by one position rather than producing garbage.
For :
| binary | Gray | |
|---|---|---|
| 0 | 000 | 000 |
| 1 | 001 | 001 |
| 2 | 010 | 011 |
| 3 | 011 | 010 |
| 4 | 100 | 110 |
| 5 | 101 | 111 |
| 6 | 110 | 101 |
| 7 | 111 | 100 |
Formula
The -th Gray code is
i.e. each bit is the XOR of the corresponding bit of with the next higher bit. Consecutive numbers differ only in the lowest few bits of the binary form, and XOR-ing with the shifted copy collapses those changes to a single bit.
def gray(i):
return i ^ (i >> 1)
codes = [gray(i) for i in range(8)]
assert codes == [0, 1, 3, 2, 6, 7, 5, 4]
n = 10
for i in range((1 << n) - 1):
assert bin(gray(i) ^ gray(i + 1)).count("1") == 1 # neighbours differ in one bit
assert bin(gray((1 << n) - 1) ^ gray(0)).count("1") == 1 # and it is cyclic
assert sorted(gray(i) for i in range(1 << n)) == list(range(1 << n)) # a permutationInverse: from Gray code to the index
Given , recover . The highest bit of equals that of ; each next bit of is the XOR of the next bit of with the bit of just found, i.e. the bit of at position equals the XOR of all bits of from position upwards:
def inverse_gray(g):
i = 0
while g:
i ^= g
g >>= 1
return i
assert all(inverse_gray(gray(i)) == i for i in range(1 << 12))
assert inverse_gray(0b110) == 4Applications
Enumerating all subsets with one change per step
Iterating the subsets of in Gray order changes one element per step, so a quantity depending on the subset (sum, XOR, count of violated constraints) can be updated in instead of recomputed in . The bit that flips between step and is the lowest set bit of .
def subset_sums_gray(values):
"""Sums of all subsets, generated by flipping one element per step."""
n = len(values)
total = 0
sums = [0]
current = 0 # Gray code of the step
for i in range(1, 1 << n):
bit = (i & -i).bit_length() - 1 # the element that changes
current ^= 1 << bit
total += values[bit] if current >> bit & 1 else -values[bit]
sums.append(total)
return sums
vals = [3, 5, 9, 14]
sums = subset_sums_gray(vals)
assert sorted(sums) == sorted(sum(vals[j] for j in range(4) if m >> j & 1) for m in range(16))Solving puzzles: the Towers of Hanoi and the Chinese rings
The state graph of these puzzles is a Gray code path: the optimal solution visits states in Gray order.
Error resistance in encoders and Karnaugh maps
Adjacent cells of a Karnaugh map differ in one variable because rows and columns are labelled in Gray order.
Generating a Hamiltonian cycle on the hypercube
Consecutive Gray codes are adjacent vertices of the -dimensional hypercube, so the sequence is a Hamiltonian cycle of that graph.
n = 4
edges_in_cycle = {frozenset((gray(i), gray((i + 1) % (1 << n)))) for i in range(1 << n)}
assert len(edges_in_cycle) == 1 << n
assert all(bin(a ^ b).count("1") == 1 for a, b in map(tuple, edges_in_cycle))