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)
Nie mogę zagwarantować, że pozostanie w 2x optymalnym programie dla liczb parzystych, ale jest wydajny. Dla wszystkich prawidłowych danych wejściowych jest 0 <= n < 65536
to w zasadzie natychmiastowe i generuje program składający się maksymalnie z 33 instrukcji. W przypadku dowolnego rozmiaru rejestru n
(po ustaleniu tej stałej) zajęłoby to O(n)
najwyżej trochę czasu2n+1
instrukcje.
Trochę logiki binarnej
Dowolną liczbę nieparzystą n
można osiągnąć w ciągu 31 kroków: wykonaj y+=x
, otrzymaj x,y = 1,1
, a następnie podwojaj za x
pomocą x+=x
(dla pierwszego podwojenia x+=y
, ponieważ początkowo x
jest nieparzysta). x
osiągnie w ten sposób każdą potęgę 2 (to tylko przesunięcie w lewo), więc możesz ustawić dowolną wartośćy
1, dodając odpowiednią potęgę 2. Ponieważ używamy rejestrów 16-bitowych i każdy bit oprócz dla pierwszego potrzeba jednego podwojenia, aby osiągnąć, a drugiego y+=x
do ustawienia, otrzymujemy maksymalnie 31 operacji.
Dowolna liczba parzysta n
to tylko potęga 2, zadzwoń a
, razy liczbę nieparzystą, zadzwoń m
; tj. n = 2^a * m
lub równoważnie n = m << a
. Użyj powyższego procesu, aby uzyskać m
, a następnie zresetuj x
, przesuwając go w lewo, aż osiągnie wartość 0. Wykonaj a, x+=y
aby ustawić x = m
, a następnie kontynuuj podwojenie x
, przy pierwszym użyciu, x+=y
a następnie przy użyciu x+=x
.
Cokolwiek a
to jest, potrzeba 16-a
zmian, x
aby uzyskać y=m
i dodatkowych a
zmian, aby zresetować x=0
. Kolejne a
zmiany x
nastąpią po x=m
. 16+a
Wykorzystano więc całkowitą liczbę zmian. Istnieje kilka 16-a
bitów, które należy ustawić, aby je zdobyć m
, a każdy z nich zajmie jeden y+=x
. Wreszcie potrzebujemy dodatkowego kroku, x=0
aby ustawić go na m,x+=y
. Aby uzyskać dowolną liczbę parzystą, potrzeba maksymalnie 33 kroków.
Możesz oczywiście uogólnić to na dowolny rejestr wielkości, w którym to przypadku zawsze zajmuje najwyżej 2n-1
i 2n+1
operuje na parzyste i nieparzysten
operuje odpowiednio na liczbach .
Optymalność
Ten algorytm tworzy program, który jest prawie optymalny (tzn. 2n+2
Jeśli if n
jest minimalną liczbą kroków) dla liczb nieparzystych. Dla danej liczby nieparzystej n
, jeśli ten m
bit jest wiodącym 1, to dowolny program podejmuje przynajmniej m
kroki, aby dostać się do x=n
lub y=n
, ponieważ operacja, która najszybciej zwiększa wartości rejestrów, to x+=x
lub y+=y
(tj. Podwojenie) i potrzeba m
podwojenia, aby dostać się do część m
z 1. Ponieważ ten algorytm wykonuje co najwyżej 2m
kilka kroków (maksymalnie dwa na podwojenie, jeden dla zmiany i jedeny+=x
), każda liczba nieparzysta jest reprezentowana prawie optymalnie.
Liczby parzyste nie są tak dobre, ponieważ zawsze resetuje 16 operacji x
zanim cokolwiek innego, a na przykład 8 można osiągnąć w ciągu 5 kroków.
Co ciekawe, powyższy algorytm nigdy nie używa y+=y
w ogóley
zawsze jest nieparzysty. W takim przypadku może faktycznie znaleźć najkrótszy program dla ograniczonego zestawu tylko 3 operacji.
Testowanie
# 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()
Napisałem prosty test, aby sprawdzić, czy moje rozwiązanie rzeczywiście daje poprawne wyniki i nigdy nie przekracza 33 kroków, dla wszystkich prawidłowych danych wejściowych ( 0 <= n < 65536
).
Ponadto próbowałem przeprowadzić analizę empiryczną, aby porównać wyniki mojego rozwiązania z wynikami optymalnymi - okazuje się jednak, że wyszukiwanie z szerokością pierwszego rzędu jest zbyt mało wydajne, aby uzyskać minimalną długość wyjściową dla każdego ważnego wejścia n
. Na przykład użycie BFS do znalezienia wyjścia n = 65535
nie kończy się w rozsądnym czasie. Niemniej jednak wyszedłem bfs()
i jestem otwarty na sugestie.
Testowałem jednak swoje własne rozwiązanie pod kątem @ CChak (zaimplementowane w Pythonie tutaj jako U
). Spodziewałem się, że moja będzie gorzej, ponieważ jest drastycznie nieefektywna dla mniejszych liczb parzystych, ale uśredniona dla całego zakresu na dwa sposoby, kopalnia produkuje średnio o 10,8% do 12,3% mniej. Pomyślałem, że może to wynikało z lepszej wydajności z mojego własnego rozwiązania dla liczb nieparzystych, więc V
używa moich na liczbach nieparzystych i @ CChak na liczbach parzystych, ale V
jest pomiędzy (około 10% krótszy niż U
, 3% dłuższy niż S
).
x+=x
legalne tylko wtedy, gdyx
jest parzyste? Myślę też, że w przypadku najkrótszego programu coś takiego jak BFS może działać.