Пітон, 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)
Я не можу гарантувати, що це залишається в 2 рази оптимальною програмою для парних чисел, але це ефективно. Для всіх дійсних даних 0 <= n < 65536
це по суті миттєво і генерує програму щонайменше 33 інструкцій. Для довільного розміру реєстру n
(після фіксації цієї константи) знадобиться O(n)
час, максимум 2n+1
інструкцій.
Деякі бінарні логіки
Будь-яке непарне число n
може бути досягнуто в протягом 31 кроків: робити y+=x
, отримувати x,y = 1,1
, а потім продовжує подвоюючи x
з x+=x
(для першого подвоєння зробити x+=y
, так як x
непарній , щоб почати с). x
таким чином досягнемо кожної потужності 2 (це лише зсув ліворуч), і тому ви можете встановити будь-який біт y
на 1, додавши відповідну потужність 2. Оскільки ми використовуємо 16-бітні регістри, і кожен біт, за винятком для першого потрібно одного подвоєння, щоб досягти, і одного y+=x
для встановлення, ми отримуємо максимум 31 ops.
Будь-яке парне число n
- це лише деяка потужність 2, називайте його a
, разів непарне число, називайте його m
; тобто n = 2^a * m
, чи , що еквівалентно, n = m << a
. Скористайтеся вищевказаним процесом, щоб отримати m
, а потім скиньте x
його, переміщуючи його ліворуч, до досягнення 0. Зробіть a, x+=y
щоб встановити x = m
, а потім продовжуйте подвоювати x
, вперше використовуючи x+=y
та згодом використовуючи x+=x
.
Незалежно a
, воно приймає 16-a
зрушення , x
щоб отримати y=m
і додаткові a
зміни для скидання x=0
. Наступні a
зрушення x
відбудуться після x=m
. Тож використовується загальна 16+a
зміна. Існує до 16-a
бітів, які потрібно встановити m
, і кожен з них візьме один y+=x
. Нарешті , ми повинні додатковий крок , коли x=0
встановити його в м x+=y
. Тому потрібно отримати не більше 33 кроків, щоб отримати будь-яке парне число.
Звичайно, ви можете узагальнити це до будь-якого регістру розмірів, і в цьому випадку він завжди має максимум 2n-1
і 2n+1
ops для непарних і парних n
цілих чисел відповідно.
Оптимальність
Цей алгоритм виробляє програму, яка є майже оптимальною (тобто в межах, 2n+2
якщо n
є мінімальна кількість кроків) для непарних чисел. Для заданого непарного числа n
, якщо m
й біт є провідним 1, то будь-яка програма займає щонайменше m
кроки , щоб дістатися до x=n
або y=n
, так як операції , яка збільшує значення в регістрах швидкий є x+=x
або y+=y
(тобто подвоєнь) і він приймає m
подвоєнь , щоб дістатися до m
й біт від 1. Так як цей алгоритм приймає на більшості 2m
стадій ( НЕ більше двох за подвоєння, один для зсуву і один y+=x
), будь-який непарне число представляється майже оптимальним чином .
Навіть цифри не настільки хороші, оскільки для скидання завжди використовується 16 ops, x
перш ніж будь-що інше, а 8, наприклад, можна досягти протягом 5 кроків.
Цікаво, що наведений вище алгоритм взагалі ніколи не використовується y+=y
, оскільки y
завжди залишається непарним. У цьому випадку вона може фактично знайти найкоротшу програму для обмеженого набору лише з 3 операцій.
Тестування
# 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()
Я написав простий тест, щоб перевірити, чи справді моє рішення дає правильні результати, і ніколи не проходить понад 33 кроки для всіх дійсних входів ( 0 <= n < 65536
).
Крім того, я спробував зробити емпіричний аналіз для порівняння результатів мого рішення з оптимальними результатами - однак, виявляється, що пошук в першу чергу є надто неефективним, щоб отримати мінімальну довжину виходу для кожного дійсного вводу n
. Наприклад, використання BFS для пошуку результату n = 65535
не закінчується за розумну кількість часу. Тим не менш, я пішов bfs()
і я відкритий для пропозицій.
Я проте протестував власне рішення проти @ CChak's (реалізований у Python тут як U
). Я очікував, що міна зробить гірше, оскільки вона є різко неефективною для менших парних чисел, але в середньому по всьому діапазону двома способами, шахта дала вихід довжини в середньому на 10,8% до 12,3% коротше. Я думав, можливо, це пов’язано з кращою ефективністю мого власного рішення на непарних числах, такV
використовує міну на непарні числа і @ CChak на парні числа, але V
знаходиться між ними (приблизно на 10% коротше U
, на 3% довше, ніж S
).
x+=x
законний лише якщоx
рівний? Також для найкоротшої програми я думаю, що щось на зразок BFS могло б працювати.