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)
Ich kann nicht garantieren, dass dies innerhalb des 2x optimalen Programms für gerade Zahlen bleibt, aber es ist effizient. Für alle gültigen Eingaben 0 <= n < 65536
erfolgt dies im Wesentlichen sofort und es wird ein Programm mit höchstens 33 Anweisungen erstellt. Für eine beliebige Registergröße n
(nach dem Fixieren dieser Konstante) würde es O(n)
mit höchstens 2n+1
Befehlen einige Zeit dauern .
Eine binäre Logik
Jede ungerade Zahl n
kann in 31 Schritten erreicht werden: doy+=x
, immer x,y = 1,1
, und dann immer wieder zu verdoppeln x
mit x+=x
(für die erste Verdoppelung tun x+=y
, da x
ungerade zu beginnen). x
Auf diese Weise wird jede Potenz von 2 erreicht (es ist nur eine Linksverschiebung), und Sie können jedes Bit y
auf 1 setzen, indem Sie die entsprechende Potenz von 2 addieren. Da wir 16-Bit-Register verwenden, und jedes Bit mit Ausnahme von für das erste braucht man eine Verdopplung, um zu erreichen und eine y+=x
zu setzen, wir bekommen maximal 31 Ops.
Jede gerade Zahl n
ist nur eine Potenz von 2, nenne es a
mal eine ungerade Zahl, nenne es m
; dh n = 2^a * m
oder gleichwertign = m << a
. Verwenden Sie den obigen Prozess, um zu erhalten m
, und setzen Sie ihn dann zurück, x
indem Sie ihn nach links verschieben, bis er 0 ist. x+=y
Setzen Sie a x = m
, und verdoppeln Sie dann, wenn Sie x
zum ersten Mal x+=y
und anschließend verwenden x+=x
.
Was auch immer a
ist, es braucht 16-a
Schichten x
, um zu kommen y=m
und zusätzliche a
Schichten, um zurückgesetzt zu werden x=0
. Weitere a
Verschiebungen von x
werden danach auftretenx=m
. Es werden also insgesamt 16+a
Schichten verwendet. Es gibt bis zu 16-a
Bits, die gesetzt werden müssen, um zu erhalten m
, und jeder von ihnen benötigt eines y+=x
. Zum Schluss brauchen wir noch einen zusätzlichen Schritt x=0
, um m x+=y
,. Es dauert also höchstens 33 Schritte, um eine gerade Zahl zu erhalten.
Sie können dies natürlich auf ein beliebiges Größenregister verallgemeinern. In diesem Fall werden für Ganzzahlen mit ungeraden und geraden Bits immer höchstens 2n-1
und 2n+1
ops verwendet n
.
Optimalität
Dieser Algorithmus erzeugt ein Programm , das nahezu optimal ist (dh innerhalb 2n+2
wenn n
ist die minimale Anzahl von Schritten) für ungerade Zahlen. Wenn für eine gegebene ungerade Zahl n
das m
th-Bit die führende 1 ist, unternimmt jedes Programm mindestens m
Schritte, um zu x=n
oder zu gelangen y=n
, da die Operation, die die Werte der Register am schnellsten erhöht, x+=x
oder y+=y
(dh Verdopplungen) ist und es dauertm
Verdopplungen , um zu gelangen das m
th-Bit von 1. Da dieser Algorithmus höchstens 2m
Schritte benötigt (höchstens zwei pro Verdopplung, einer für die Verschiebung und einer y+=x
), wird jede ungerade Zahl nahezu optimal dargestellt.
Gerade Zahlen sind nicht ganz so gut, da immer 16 Operationen zum Zurücksetzen verwendet werden x
, und 8 zum Beispiel in 5 Schritten erreicht werden können.
Interessanterweise wird der obige Algorithmus überhaupt nicht verwendet y+=y
, da er y
immer ungerade ist. In diesem Fall wird möglicherweise das kürzeste Programm für den eingeschränkten Satz von nur 3 Operationen gefunden.
Testen
# 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()
Ich habe einen einfachen Test geschrieben, um zu überprüfen, ob meine Lösung tatsächlich für alle gültigen Eingaben ( 0 <= n < 65536
) korrekte Ergebnisse liefert und nie mehr als 33 Schritte durchläuft .
Außerdem habe ich versucht, eine empirische Analyse durchzuführen, um die Ausgabe meiner Lösung mit den optimalen Ausgaben zu vergleichen. Es hat sich jedoch herausgestellt, dass die Breitensuche zu ineffizient ist, um die minimale Ausgabelänge für jede gültige Eingabe zu ermitteln n
. Die Verwendung von BFS zum Ermitteln der Ausgabe für n = 65535
wird beispielsweise nicht in angemessener Zeit beendet. Trotzdem bin ich bfs()
offen für Vorschläge.
Ich habe jedoch meine eigene Lösung gegen @ CChak's getestet (implementiert in Python hier als U
). Ich habe damit gerechnet, dass sich meine Leistung verschlechtern würde, da sie für kleinere gerade Zahlen drastisch ineffizient ist, aber über den gesamten Bereich auf zwei Arten gemittelt wird. Meine Leistung war durchschnittlich 10,8% bis 12,3% kürzer. Ich dachte, dies V
liege möglicherweise an der besseren Effizienz meiner eigenen Lösung für ungerade Zahlen. Daher wird meine für ungerade Zahlen und @ CChak für gerade Zahlen verwendet, liegt aber V
dazwischen (etwa 10% kürzer als U
, 3% länger als S
).
x+=x
nur legal, wennx
es gerade ist? Auch für das kürzeste Programm denke ich, dass so etwas wie BFS funktionieren könnte.