Unit 3: Bits
A whole set inside one number.
Unit 3 of 25 in Competitive programming for kids. Its 4 lessons are Numbers Are Rows of Switches, And, Or, Xor, Shift, A Set Inside One Number and The Number That Came Alone — below is everything each one explains, and a question or two from it to try.
Every sample on this page was run through real Python before it shipped, and prints exactly what it says it prints.
This unit opens with a fortnight’s trial of everything — no card needed — or with a family plan, bought in the iPhone app. The first two units of every track are free for ever. Try it in the app.
🔢 Numbers Are Rows of Switches
Every number is already binary underneath
A computer stores 13 as switches: 1101. The places are worth 8, 4, 2 and 1, and 8 + 4 + 1 is 13.
Python will show you, and the 0b in front just means "this is binary".
Python
for n in [1, 2, 5, 13, 255]:
print(n, bin(n))
It prints
1 0b1 2 0b10 5 0b101 13 0b1101 255 0b11111111
Reading one switch
Shifting right by i slides bit number i down to the bottom, and & 1 keeps only the bottom one. So (x >> i) & 1 is bit i, and it is the most-used line in this unit.
Bit 0 is the ones place, bit 1 the twos, bit 2 the fours.
Python
x = 13
for i in range(4):
print(i, (x >> i) & 1)
It prints
0 1 1 0 2 1 3 1
And building one back up
Going the other way, each new bit doubles what you had and adds itself. Same idea as reading a decimal number left to right, with 2 instead of 10.
Python
bits = [1, 1, 0, 1]
value = 0
for b in bits:
value = value * 2 + b
print(value)
It prints
13
Try it yourself
Which bit is the only one switched on in the number 8?
- Bit 8
- Bit 4
- Bit 3
- Bit 1
What does this print?
Python
x = 37
print(bin(x), (x >> 0) & 1, (x >> 1) & 1, (x >> 2) & 1)
Answer them in the app
⚙️ And, Or, Xor, Shift
Four operators that work on every switch at once
& keeps a bit only when both numbers have it. | keeps it when either does. ^ — xor — keeps it when exactly one does.
One machine instruction, sixty-four switches, no loop.
Python
a = 12
b = 10
print(bin(a), bin(b))
print(a & b, a | b, a ^ b)
It prints
0b1100 0b1010 8 14 6
Shifting is multiplying and dividing by two
x << 1 slides every bit up one place, which doubles it. x >> 2 slides down two places, which quarters it and throws the leftovers away — exactly like //.
1 << n is the fastest way to write two to the power of n, and you will see it everywhere from here on.
Python
x = 5
print(x << 1, x << 3, 40 >> 2, 41 >> 2)
print(1 << 10, 2 ** 10)
It prints
10 40 10 10 1024 1024
Two tricks worth memorising
x & (x - 1) switches off the lowest bit that is on. Do it until nothing is left and you have counted the ones.
x & -x gives you that lowest bit on its own, as a number.
Python
x = 44
print(bin(x))
print(bin(x & (x - 1)))
print(x & -x)
count = 0
while x > 0:
x &= x - 1
count += 1
print(count)
It prints
0b101100 0b101000 4 3
Xor is its own undo
Xor a number with the same thing twice and you are back where you started, because every switch got flipped and flipped again.
And x ^ 0 is x. Hold on to both — the boss lesson is built entirely out of them.
Python
print(7 ^ 7, 7 ^ 0, (3 ^ 5) ^ 5)
It prints
0 7 3
Try it yourself
What is x & 1 good for?
- Telling whether `x` is odd
- Halving `x`
- Telling whether `x` is negative
- Counting the bits of `x`
What does this print?
Python
print(6 & 3, 6 | 3, 6 ^ 3, 6 << 2)
Answer them in the app
🎭 A Set Inside One Number
Bit i means "item i is in the group"
Three animals, so three bits. The number 5 is 101, so the group is the ant and the cat.
A whole subset, stored as one small number. This is called a bitmask.
Python
items = ["ant", "bee", "cat"]
mask = 5
chosen = []
for i in range(3):
if (mask >> i) & 1:
chosen.append(items[i])
print(mask, bin(mask), chosen)
It prints
5 0b101 ['ant', 'cat']
So every subset is just a number to count up to
Unit 2 walked the subsets with recursion. With masks it is a plain for loop from 0 to 2 ** n — and the numbers come out in a fixed, easy-to-reason-about order.
1 << n is 2 ** n, and it is what everybody writes.
Python
items = [1, 2, 3]
for mask in range(1 << len(items)):
chosen = [items[i] for i in range(len(items)) if (mask >> i) & 1]
print(mask, chosen)
It prints
0 [] 1 [1] 2 [2] 3 [1, 2] 4 [3] 5 [1, 3] 6 [2, 3] 7 [1, 2, 3]
The set operations come free
Union is |. Intersection is &. "In a but not in b" is a & ~b. Adding item i is mask | (1 << i), and asking whether it is in is mask & (1 << i).
Every one of those is a single instruction, which is why a bitmask beats a real set in a contest.
Python
a = 0b1011
b = 0b0110
print(bin(a | b), bin(a & b), bin(a & ~b))
print(bin(a | (1 << 2)), (a & (1 << 2)) != 0)
It prints
0b1111 0b10 0b1001 0b1111 False
Try it yourself
A mask of 6 over the items ["ant", "bee", "cat"] means which group?
- ant and bee
- bee and cat
- ant and cat
- all three
What does this print?
Python
numbers = [2, 3, 5]
best = 0
for mask in range(1 << 3):
total = 0
for i in range(3):
if (mask >> i) & 1:
total += numbers[i]
if total <= 6 and total > best:
best = total
print(best)
Answer them in the app
🏆 The Number That Came Alone
A million numbers, all in pairs but one
Everyone at the party came with a partner, except one person. Find them.
The obvious plan is to count how many times each number appears. That works, and it costs memory the problem may not give you. There is a way with no memory at all.
Xor everything together
Xor undoes itself, and it does not care what order things arrive in. So every pair cancels to 0, and 0 xor the lonely number is the lonely number.
One pass, one variable. This is the trick that makes people love bits.
Python
numbers = [4, 7, 1, 4, 7]
answer = 0
for x in numbers:
answer ^= x
print(answer)
It prints
1
Why the order cannot matter
Xor works bit by bit, and each bit of the answer is simply "was this bit on an odd number of times?". Shuffling the list does not change that count.
So the pairs cancel wherever they are, even split up across the list.
Python
a = [4, 7, 1, 4, 7]
b = [7, 4, 4, 1, 7]
def fold(numbers):
answer = 0
for x in numbers:
answer ^= x
return answer
print(fold(a), fold(b))
It prints
1 1
One more, because it turns up constantly
A number is a power of two exactly when it has a single bit on — and knocking that bit off with x & (x - 1) leaves nothing.
Contest code is full of one-line tests like this. They are worth recognising even when you would write the slow version yourself.
Python
for x in [1, 2, 3, 8, 12, 64]:
print(x, x & (x - 1) == 0)
It prints
1 True 2 True 3 False 8 True 12 False 64 True
Try it yourself
Two numbers are lonely instead of one. Does xoring everything still find them?
- Yes, it prints both
- No — it gives the two of them xored together, which is neither of them
- Yes, it prints the larger one
- No, it gives 0
What does this print?
Python
numbers = [9, 2, 9, 5, 2]
answer = 0
for x in numbers:
answer ^= x
print(answer)
Answer them in the app