Machen Sie eine beliebige Zahl, indem Sie wiederholt 2 Zahlen hinzufügen

14

Sie erhalten eine Maschine mit zwei 16-Bit-Registern xund y. Die Register werden initialisiert x=1und y=0. Die einzige Operation, die die Maschine ausführen kann, ist das Hinzufügen von Modulo 65536. Das heißt:

  • x+=y- xwird ersetzt durch (x + y) mod 65536; yist unverändert
  • y+=x - ähnlich für y
  • x+=x- xwird ersetzt durch 2x mod 65536; legal nur wenn xes gerade ist
  • y+=y - ähnlich für y

Das Ziel ist es, eine vorbestimmte Anzahl in einem der Register (entweder xoder y) zu erhalten.

Schreibe ein Programm oder ein Unterprogramm , das eine Reihe empfängt (in stdin, argv, Funktionsparameter Oberseite des Stapels oder einer anderen konventionellen Stelle), und gibt ein Programm diese Zahl zu erhalten. Die Ausgabe sollte an ein anderes herkömmliches Ausgabegerät gehen stdoutoder (falls Ihre Sprache keine hat stdout) an ein anderes herkömmliches Ausgabegerät.

Das Ausgabeprogramm kann bis zu 100% plus 2 Schritte weit vom Optimum entfernt sein. Das heißt, wenn das kürzeste Programm zum Abrufen der Zielnummer nSchritte enthält, kann Ihre Lösung nicht länger sein als 2n+2. Diese Einschränkung dient dazu, "zu einfache" Lösungen (z. B. Zählen von 1, 2, 3, ...) zu vermeiden, erfordert jedoch keine vollständige Optimierung. Ich gehe davon aus, dass das kürzeste Programm am einfachsten zu finden ist, kann aber nicht sicher sein ...

Zum Beispiel: Eingabe = 25. Ausgabe:

y+=x
x+=y
x+=y
x+=x
x+=x
x+=x
y+=x

Ein weiteres Beispiel: Für jede Fibonacci-Zahl hat die Ausgabe dieses Wechselmuster. Für Eingabe = 21 ist Ausgabe

y+=x
x+=y
y+=x
x+=y
y+=x
x+=y
y+=x

Der kürzeste Code (gemessen in Bytes) gewinnt.

(Dieses Puzzle wurde von einem Code für einen 16-Bit-Prozessor inspiriert, den ich kürzlich generieren musste.)

PS Ich frage mich - für welche Nummer ist das optimale Programm am längsten?

anatolyg
quelle
9
Warum ist aus Neugier x+=xnur legal, wenn xes gerade ist? Auch für das kürzeste Programm denke ich, dass so etwas wie BFS funktionieren könnte.
Sp3000
Wenn man am Ziel angekommen ist, möchte man vielleicht weiter zur nächsten Zielnummer gehen. Um zu einem Ziel zu gelangen, muss eine der Zahlen ungerade sein. Ich wollte keinen endlosen Strom von Zielen
erzeugen
Ich habe die Beschränkung der Anzahl der Schritte geändert, sodass das Ausgabeprogramm für die Zielnummer 0 oder 1 nicht leer sein muss.
Anatolyg
3
wenn x+=xnur für gerade xs funktioniert, wie kommt es, dass das beispiel für eine eingabe von 25 3 verdoppelt?
bcsb1001

Antworten:

2

CJam, 31

Wie @Tobia ‚s Antwort, mein Algorithmus auch schamlos gestohlen inspiriert @CChak die Antwort‘. Mit der schwarzen Magie von CJam gelang es mir jedoch, den Algorithmus noch kleiner zu implementieren.

Probieren Sie es hier aus.

Golf gespielt:

qi{_4%!:X)/X!-'xX+"
y+="@}h;]W%`

Ungolfed:

qi          "Read input and convert to integer.";
{           "Do...";
  _4%!:X    "Assign (value mod 4 == 0) to X.";
  )/X!-     "If X, divide value by 2. If not X, decrement value.";
  'xX+      "If X, put 'y' on the stack. If not X, put 'x' on the stack.";
  "
y+="        "Put '\ny+=' on the stack.";
  @         "Rotate top 3 elements of stack left so the value is on top.";
}h          "... while value is not zero.";
;           "Discard zero value on stack.";
]W%         "Collect stack into array and reverse.";

Bitte korrigieren Sie mich, wenn ich falsch liege, aber ich dachte, dass die Modulo 65536-Operation, die in Antworten mit einem ähnlichen Algorithmus verwendet wird, nicht erforderlich ist. Ich habe die Frage so interpretiert, dass wir davon ausgehen können, dass die Eingabe eine gültige 16-Bit-Ganzzahl ohne Vorzeichen ist, und dass alle Zwischenwerte oder Ergebnisse dieses Algorithmus dies auch sein werden.

Runer112
quelle
Ein interessanter Punkt zur Entfernung von Mod 65536. Ich denke, Sie machen einen guten Fall, dass die "vorgegebene Zahl" innerhalb von 0-65535 liegen muss.
CChak,
8

Perl 107 97

Erster Beitrag, also hier geht's.

sub h{($i)=@_;return if(($i%=65536)==0);($i%4==0)?do{h($i/2);say"y+=y";}:do{h($i-1);say"y+=x";}}

Dies entspricht allen Kriterien für das Hinzufügen von Registern, aber ich habe nicht vollständig überprüft, ob meine Antwort immer innerhalb von 2n + 2 der optimalen Anzahl von Schritten lag. Für jede Fibonacci-Zahl liegt es jedoch deutlich innerhalb des Grenzwerts.

Hier ist eine detailliertere Aufschlüsselung

sub h{                           # Declare the subroutine, it should be called referencing an integer value
   ($i)=@_;                      # Assign the i variable to the integer used in the call
   return if(($i%=65536)==0);    # Check for base condition of called by 0, and enforce modulo from the start.
   ($i%4==0) ?                   # If the value passed is even, and will result in an even number if we halve it
   do{h($i/2);say"y+=y";}        # Then do so and recurse 
   :do{h($i-1);say"y+=x";}       # Otherwise "subtract one" and recurse
}                                # Note that the print statements get called in reverse order as we exit.

Wie ich bereits erwähnte, ist dies mein erster Versuch, Golf zu spielen. Ich bin mir also sicher, dass dies verbessert werden kann. Ich bin mir auch nicht sicher, ob der anfängliche Unterprogrammaufruf in einem rekursiven Aufruf gezählt werden muss oder nicht, was uns ein paar Zeichen in die Höhe treiben könnte.

Interessanterweise können wir den Code um 11 Byte * reduzieren und unsere "Effizienz" in Bezug auf die Anzahl der Registeroperationen verbessern, indem wir die Forderung lockern, dass nur gerade Werte "verdoppelt" werden können. Ich habe das zum Spaß hier aufgenommen:

sub h{($i)=@_;return if(($i%=65536)==0);($i%2==0)?do{h($i/2);say"y+=y";}:do{h($i-1);say"y+=x";}}

Nachtrag beginnen:

Hat mir sehr gut gefallen, und ich habe in den letzten Wochen immer wieder damit herumgespielt. Dachte ich würde meine Ergebnisse posten.

Einige Zahlen:

Unter Verwendung eines BFS-Algorithmus, um eine optimale Lösung zu finden, gibt es in den ersten 2 ^ 16 Zahlen nur 18 Zahlen, die 23 Schritte erfordern. Dies sind: 58558, 59894, 60110, 61182, 61278, 62295, 62430, 62910, 63422, 63462, 63979, 64230, 64314, 4486, 64510, 64698, 64854, 65295.

Unter Verwendung des oben beschriebenen rekursiven Algorithmus ist die "am schwierigsten zu erreichende" Zahl 65535 bei 45 Operationen. (65534 dauert 44, und es gibt 14 Zahlen, die 43 Schritte benötigen) 65535 ist auch die größte Abweichung vom Optimum, 45 vs 22. Die Differenz von 23 Schritten beträgt 2n + 1. (Nur drei Zahlen treffen auf 2n: 65534, 32767, 32751.) Mit Ausnahme der trivialen (Null-Schritt-) Fälle über den definierten Bereich ergibt die rekursive Methode einen Durchschnitt von ungefähr dem 1,4-fachen der optimalen Lösung.

Fazit: Für die Zahlen 1-2 ^ 16 überschreitet der rekursive Algorithmus niemals den definierten Schwellenwert von 2n + 2, sodass die Antwort gültig ist. Ich vermute jedoch, dass es zu weit von der optimalen Lösung für größere Register / mehr Bits abweichen würde.

Der Code, den ich zum Erstellen des BFS verwendet habe, war schlampig, speicherintensiv, nicht kommentiert und absichtlich nicht enthalten. Also ... Sie müssen meinen Ergebnissen nicht vertrauen, aber ich bin ziemlich zuversichtlich.

CChak
quelle
Eine Nicht-BFS-Lösung, großartig!
Anatolyg
Ich denke, selbst für die meisten pathologischen Fälle bleiben Sie innerhalb eines Faktors von 4, vielleicht besser (da ich nur eine Untergrenze für die optimale Lösung kenne). Welches ist immer noch ziemlich gut.
Rationalis
7

Python 3, 202 Bytes

def S(n):
 q=[(1,0,"")];k=65536
 while q:
  x,y,z=q.pop(0)
  if n in{x,y}:print(z);return
  q+=[((x+y)%k,y,z+"x+=y\n"),(x,(x+y)%k,z+"y+=x\n")]+[(2*x%k,y,z+"x+=x\n")]*(~x&1)+[(x,2*y%k,z+"y+=y\n")]*(~y&1)

(Danke an @rationalis für ein paar Bytes)

Hier ist eine sehr einfache Lösung. Ich wünschte, ich könnte die letzte Linie besser spielen, aber mir fehlen momentan die Ideen. Mit anrufen S(25).

Das Programm führt nur ein einfaches BFS ohne Zwischenspeicherung aus, ist also sehr langsam. Hier ist S(97)für einige Beispielausgaben:

y+=x
x+=y
x+=x
x+=x
x+=x
x+=x
y+=x
y+=x
x+=y
Sp3000
quelle
5

Dyalog APL, 49 Zeichen / Byte *

{0=N←⍵|⍨2*16:⍬⋄0=4|N:⎕←'y+=y'⊣∇N÷2⋄⎕←'y+=x'⊣∇N-1}

Algorithmus schamlos inspiriert von @CChaks Antwort.

Beispiel:

    {0=N←⍵|⍨2*16:⍬⋄0=4|N:⎕←'y+=y'⊣∇N÷2⋄⎕←'y+=x'⊣∇N-1} 25
y+=x
y+=x
y+=y
y+=x
y+=x
y+=y
y+=y
y+=x

Ungolfed:

{
    N←(2*16)|⍵                 # assign the argument modulo 65536 to N
    0=N: ⍬                     # if N = 0, return an empty value (that's a Zilde, not a 0)
    0=4|N: ⎕←'y+=y' ⊣ ∇N÷2     # if N mod 4 = 0, recurse with N÷2 and *then* print 'y+=y'
    ⎕←'y+=x' ⊣ ∇N-1            # otherwise, recurse with N-1 and *then* print 'y+=x'
}

* Dyalog APL unterstützt einen älteren Zeichensatz, bei dem die APL-Symbole den oberen 128-Byte-Werten zugeordnet sind. Daher kann ein APL-Programm, das nur ASCII-Zeichen und APL-Symbole verwendet, als Byte == Zeichen betrachtet werden.

Tobia
quelle
3

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 < 65536erfolgt 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+1Befehlen einige Zeit dauern .

Eine binäre Logik

Jede ungerade Zahl nkann in 31 Schritten erreicht werden: doy+=x , immer x,y = 1,1, und dann immer wieder zu verdoppeln xmit x+=x(für die erste Verdoppelung tun x+=y, da xungerade zu beginnen). xAuf diese Weise wird jede Potenz von 2 erreicht (es ist nur eine Linksverschiebung), und Sie können jedes Bit yauf 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+=xzu setzen, wir bekommen maximal 31 Ops.

Jede gerade Zahl nist nur eine Potenz von 2, nenne es amal eine ungerade Zahl, nenne es m; dh n = 2^a * moder gleichwertign = m << a . Verwenden Sie den obigen Prozess, um zu erhalten m, und setzen Sie ihn dann zurück, xindem Sie ihn nach links verschieben, bis er 0 ist. x+=ySetzen Sie a x = m, und verdoppeln Sie dann, wenn Sie xzum ersten Mal x+=yund anschließend verwenden x+=x.

Was auch immer aist, es braucht 16-aSchichten x, um zu kommen y=mund zusätzliche aSchichten, um zurückgesetzt zu werden x=0. Weitere aVerschiebungen von xwerden danach auftretenx=m . Es werden also insgesamt 16+aSchichten verwendet. Es gibt bis zu 16-aBits, 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-1und 2n+1ops verwendet n.

Optimalität

Dieser Algorithmus erzeugt ein Programm , das nahezu optimal ist (dh innerhalb 2n+2wenn nist die minimale Anzahl von Schritten) für ungerade Zahlen. Wenn für eine gegebene ungerade Zahl ndas mth-Bit die führende 1 ist, unternimmt jedes Programm mindestens mSchritte, um zu x=noder zu gelangen y=n, da die Operation, die die Werte der Register am schnellsten erhöht, x+=xoder y+=y(dh Verdopplungen) ist und es dauertm Verdopplungen , um zu gelangen das mth-Bit von 1. Da dieser Algorithmus höchstens 2mSchritte 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 yimmer 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 = 65535wird 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 Vliege 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 Vdazwischen (etwa 10% kürzer als U, 3% länger als S).

rationalis
quelle
1
Sehr viel Logik in 201 Bytes!
Anatolyg
@analtolyg Was soll ich sagen, ich mag Mathe und ein bisschen Geige. Ich kann andere Ansätze untersuchen, da die Lösung für gerade Zahlen Raum für Verbesserungen bietet.
Rationalis
Ich wusste nicht einmal, dass x,y='xy'es bis jetzt möglich war. Leider kann ich mir keine Möglichkeit c*b+e*2vorstellen, mit der %Formatierung kurz und bündig zu schreiben .
Rationalis
Ah, ich wusste nicht, dass du es woanders benutzt hast. Ist es nur ich oder ist S(2)die Ausgabe wirklich lang?
Sp3000
Leider dauert bei meiner Lösung jede gerade Zahl mindestens 19 Schritte ( S(2)wobei die kürzeste bei 19 ist). Ich verfolge xund yexplizite nicht, so dass es, obwohl es xnach dem zweiten Schritt 2 erreicht, trotzdem weiter auf x0 zurückgesetzt wird . Ich habe das Gefühl, dass es eine bessere Lösung geben muss, aber bis jetzt fällt mir nichts ein einer.
Rationalis