Python, 183
def S(n):
b,c,e=16,'x+=x\n','x+=y\n';s=d='y+=x\n';a=i=0
if n<2:return
while~n&1:n>>=1;a+=1
while n:n>>=1;s+=[e,c][i]+d*(n&1);i=1;b-=1
while a:s+=[c,c*b+e*2][i];i=0;a-=1
print(s)
I can't guarantee this stays within 2x the optimal program for even numbers, but it is efficient. For all valid inputs 0 <= n < 65536
, it's essentially instantaneous, and generates a program of at most 33 instructions. For an arbitrary register size n
(after fixing that constant), it would take O(n)
time with at most 2n+1
instructions.
Some Binary Logic
Any odd number n
can be reached within 31 steps: do y+=x
, getting x,y = 1,1
, and then keep doubling x
with x+=x
(for the first doubling do x+=y
, since x
is odd to begin with). x
will reach every power of 2 this way (it's just a left-shift), and so you can set any bit of y
to be 1 by adding the corresponding power of 2. Since we're using 16-bit registers, and each bit except for the first takes one doubling to reach and one y+=x
to set, we get a maximum of 31 ops.
Any even number n
is just some power of 2, call it a
, times an odd number, call it m
; i.e. n = 2^a * m
, or equivalently, n = m << a
. Use the above process to get m
, then reset x
by left-shifting it until it's 0. Do a x+=y
to set x = m
, and then continue to double x
, the first time using x+=y
and subsequently using x+=x
.
Whatever a
is, it takes 16-a
shifts of x
to get y=m
and an additional a
shifts to reset x=0
. Another a
shifts of x
will occur after x=m
. So a total of 16+a
shifts is used. There are up to 16-a
bits that need to be set to get m
, and each of those will take one y+=x
. Finally we need an extra step when x=0
to set it to m, x+=y
. So it takes at most 33 steps to get any even number.
You can, of course, generalize this to any size register, in which case it always takes at most 2n-1
and 2n+1
ops for odd and even n
-bit integers, respectively.
Optimality
This algorithm produces a program that is near-optimal (i.e. within 2n+2
if n
is the minimum number of steps) for odd numbers. For a given odd number n
, if the m
th bit is the leading 1, then any program takes at least m
steps to get to x=n
or y=n
, since the operation which increases the registers' values fastest is x+=x
or y+=y
(i.e. doublings) and it takes m
doublings to get to the m
th bit from 1. Since this algorithm takes at most 2m
steps (at most two per doubling, one for the shift and one y+=x
), any odd number is represented near-optimally.
Even numbers are not quite so good, since it always uses 16 ops to reset x
before anything else, and 8, for example, can be reached within 5 steps.
Interestingly, the above algorithm never uses y+=y
at all, since y
is always kept odd. In which case, it may actually find the shortest program for the restricted set of only 3 operations.
Testing
# Do an exhaustive breadth-first search to find the shortest program for
# each valid input
def bfs():
d = {(0,1):0}
k = 0xFFFF
s = set(range(k+1))
current = [(0,1)]
nexts = []
def add(pt, dist, n):
if pt in d: return
d[pt] = dist
s.difference_update(pt)
n.append(pt)
i = 0
while len(s) > 0:
i += 1
for p in current:
x,y = p
add((x,x+y&k), i, nexts)
add((y,x+y&k), i, nexts)
if y%2 == 0: add(tuple(sorted((x,y+y&k))), i, nexts)
if x%2 == 0: add(tuple(sorted((x+x&k,y))), i, nexts)
current = nexts
nexts = []
print(len(d),len(s))
# Mine (@rationalis)
def S(n):
b,c,e=16,'x+=x\n','x+=y\n';s=d='y+=x\n';a=i=0
if n<2:return ''
while~n&1:n>>=1;a+=1
while n:n>>=1;s+=[e,c][i]+d*(n&1);i=1;b-=1
while a:s+=[c,c*b+e*2][i];i=0;a-=1
return s
# @CChak's approach
def U(i):
if i<1:return ''
return U(i//2)+'y+=y\n' if i%4==0 else U(i-1)+'y+=x\n'
# Use mine on odd numbers and @CChak's on even numbers
def V(i):
return S(i) if i % 2 == 1 else U(i)
# Simulate a program in the hypothetical machine language
def T(s):
x,y = 1,0
for l in s.split():
if l == 'x+=x':
if x % 2 == 1: return 1,0
x += x
elif l == 'y+=y':
if y % 2 == 1: return 1,0
y += y
elif l == 'x+=y': x += y
elif l == 'y+=x': y += x
x %= 1<<16
y %= 1<<16
return x,y
# Test a solution on all values 0 to 65535 inclusive
# Max op limit only for my own solution
def test(f):
max_ops = 33 if f==S else 1000
for i in range(1<<16):
s = f(i); t = T(s)
if i not in t or len(s)//5 > max_ops:
print(s,i,t)
break
# Compare two solutions
def test2(f,g):
lf = [len(f(i)) for i in range(2,1<<16)]
lg = [len(g(i)) for i in range(2,1<<16)]
l = [lf[i]/lg[i] for i in range(len(lf))]
print(sum(l)/len(l))
print(sum(lf)/sum(lg))
# Test by default if script is executed
def main():
test()
if __name__ == '__main__':
main()
I wrote a simple test to check that my solution indeed produces correct results, and never goes over 33 steps, for all valid inputs (0 <= n < 65536
).
In addition, I attempted to do an empirical analysis to compare my solution's output against the optimal outputs -- however, it turns out that breadth-first search is too inefficient to obtain the minimum output length for every valid input n
. For example, using BFS to find the output for n = 65535
does not terminate in a reasonable amount of time. Nonetheless I've left in bfs()
and am open to suggestions.
I did, however, test my own solution against @CChak's (implemented in Python here as U
). I expected mine would do worse, since it is drastically inefficient for smaller even numbers, but averaged across the whole range in two ways, mine produced output of length on average 10.8% to 12.3% shorter. I thought maybe this was due to better efficiency from my own solution on odd numbers, so V
uses mine on odd numbers and @CChak's on even numbers, but V
is in between (about 10% shorter than U
, 3% longer than S
).
9Out of curiosity, why is
x+=x
legal only ifx
is even? Also for the shortest program I think something like BFS could work. – Sp3000 – 2014-12-28T20:15:03.857After arriving to the target, one might want to keep going to the next target number; to have the possiblity to get to any target, one of the numbers has to be odd. I didn't want to make an endless stream of targets to avoid unneeded complexity, but the spirit is to allow such a stream... – anatolyg – 2014-12-28T20:19:09.950
I have changed the restriction on number of steps, so for target number 0 or 1 the output program is not required to be empty. – anatolyg – 2014-12-29T08:25:13.370
3if
x+=x
only works for evenx
s, how come the example for an input of 25 doubles 3? – bcsb1001 – 2015-03-21T13:28:17.490