I understand you don't want to use decimal addition, but if you must use big endian bit order, converting back to decimal first is probably the most practical. Otherwise, you end up with unnecessary reverse
calls or awkward negative array indices
def binary (dec): # big endian bit order
if dec < 2:
return [ dec ]
else:
return binary (dec >> 1) + [ dec & 1 ]
def decimal (bin, acc = 0):
if not bin:
return acc
else:
return decimal (bin[1:], (acc << 1) + bin[0])
def increment (bin):
# sneaky cheat
return binary (decimal (bin) + 1)
for x in range (10):
print (x, binary (x), increment (binary (x)))
# 0 [0] [1]
# 1 [1] [1, 0]
# 2 [1, 0] [1, 1]
# 3 [1, 1] [1, 0, 0]
# 4 [1, 0, 0] [1, 0, 1]
# 5 [1, 0, 1] [1, 1, 0]
# 6 [1, 1, 0] [1, 1, 1]
# 7 [1, 1, 1] [1, 0, 0, 0]
# 8 [1, 0, 0, 0] [1, 0, 0, 1]
# 9 [1, 0, 0, 1] [1, 0, 1, 0]
If however you can represent your binary numbers in little endian bit order, things change. Instead of converting back to decimal, increment
can be defined directly as a beautiful recursive function
def binary (dec): # little endian bit order
if dec < 2:
return [ dec ]
else:
return [ dec & 1 ] + binary (dec >> 1)
def increment (bin):
if not bin:
return [1]
elif bin[0] == 0:
return [1] + bin[1:]
else:
return [0] + increment(bin[1:])
for x in range (10):
print (x, binary (x), increment (binary (x)))
# 0 [0] [1]
# 1 [1] [0, 1]
# 2 [0, 1] [1, 1]
# 3 [1, 1] [0, 0, 1]
# 4 [0, 0, 1] [1, 0, 1]
# 5 [1, 0, 1] [0, 1, 1]
# 6 [0, 1, 1] [1, 1, 1]
# 7 [1, 1, 1] [0, 0, 0, 1]
# 8 [0, 0, 0, 1] [1, 0, 0, 1]
# 9 [1, 0, 0, 1] [0, 1, 0, 1]
Aside: converting the little endian representation back to decimal is a little different. I provide this to show that use cases for recursion exist everywhere
def decimal (bin, power = 0):
if not bin:
return 0
else:
return (bin[0] << power) + decimal (bin[1:], power + 1)
This part of the answer gives you cake and allows you to eat it too. You get big endian bit order and a recursive increment
that steps through the bits in left-to-right order – You should use either implementation above for a number of reasons, but this aims to show you that even though your problem is complex, it's still possible to think about it recursively. No reverse
or arr[::-1]
was misused in the making of this function.
def binary (dec): # big endian bit order
if dec < 2:
return [ dec ]
else:
return binary (dec >> 1) + [ dec & 1 ]
def increment (bin, cont = lambda b, carry: [1] + b if carry else b):
if bin == [0]:
return cont ([1], 0)
elif bin == [1]:
return cont ([0], 1)
else:
n, *rest = bin
return increment (rest, lambda b, carry:
cont ([n ^ carry] + b, n & carry))
for x in range (10):
print (x, binary (x), increment (binary (x)))
# 0 [0] [1]
# 1 [1] [1, 0]
# 2 [1, 0] [1, 1]
# 3 [1, 1] [1, 0, 0]
# 4 [1, 0, 0] [1, 0, 1]
# 5 [1, 0, 1] [1, 1, 0]
# 6 [1, 1, 0] [1, 1, 1]
# 7 [1, 1, 1] [1, 0, 0, 0]
# 8 [1, 0, 0, 0] [1, 0, 0, 1]
# 9 [1, 0, 0, 1] [1, 0, 1, 0]
We start by breaking the problem up into smaller parts; n
is the first problem, and rest
is the rest of the problems. But the key to thinking with continuations (like cont
above) is to think big.
In this particular problem, n
gets updated based on whether rest
gets updated. So we immediately recur on rest
and pass a continuation that will receive the result of the subproblem. Our continuation receives the answer to the subproblem b
, and whether or not that subproblem results in a carry
.
...
else:
n, *rest = bin
return increment (rest, lambda b, carry:
cont ([n ^ carry] + b, n & carry))
The n ^ carry
and n & carry
expressions determine what the answer to this subproblem is and what the next carry will be. The following truth table shows that ^
and &
encodes our answer
and next_carry
respectively. For example, if n
is 0 and carry
is 1, the carry can be consumed. The answer
will be [1] +
the answer to the subproblem and the next carry will be 0.
n carry (answer, next_carry) n ^ carry n & carry
0 0 ([0] + b, 0) 0 0
0 1 ([1] + b, 0) 1 0
1 0 ([1] + b, 0) 1 0
1 1 ([0] + b, 1) 0 1
The base cases are simple. If the subproblem is [0]
, the answer is [1]
and no carry of 0
. If the subproblem is [1]
, then the answer is [0]
with a carry of 1
...
if bin == [0]:
return cont ([1], 0)
elif bin == [1]:
return cont ([0], 1)
Lastly, design the default continuation – if the answer to the problem b
results in a carry, simply prepend [1]
to the answer, otherwise just return the answer.
cont = lambda b, carry: [1] + b if carry else b
{ }
. If you can't find it on your screen, just add your code, and one of us will do the formatting clicks. – Filmer