Crea qualsiasi numero aggiungendo ripetutamente 2 numeri


14

Ti viene data una macchina con due registri a 16 bit xe y. I registri sono inizializzati x=1e y=0. L'unica operazione che la macchina può fare è l'aggiunta modulo 65536. Cioè:

  • x+=y- xè sostituito da (x + y) mod 65536; yè invariato
  • y+=x - allo stesso modo per y
  • x+=x- xè sostituito da 2x mod 65536; legale solo se xè pari
  • y+=y - allo stesso modo per y

L'obiettivo è ottenere un numero predeterminato in uno dei registri (o xo y).

Scrivere un programma o una subroutine che riceve un numero (in stdin, argvparametro funzione, all'inizio della pila o qualsiasi altro luogo convenzionale), ed emette un programma per ottenere questo numero. L'output dovrebbe andare a stdout, o (se la tua lingua non ha stdout) a qualsiasi altro dispositivo di output convenzionale.

Il programma di output può essere fino al 100% più 2 passi lungi dall'essere ottimale. Cioè, se il programma più breve per ottenere il numero di destinazione ha npassaggi, la soluzione non può essere più lunga di 2n+2. Questa restrizione è di evitare soluzioni "troppo facili" (ad es. Conteggio 1, 2, 3, ...) ma non richiedere l'ottimizzazione completa; Mi aspetto che il programma più breve sia il più facile da trovare, ma non posso essere sicuro ...

Ad esempio: Input = 25. Output:

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

Un altro esempio: per qualsiasi numero di fibonacci, l'output ha questo modello alternato. Per Input = 21, output è

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

Vince il codice più breve (misurato in byte).

(questo puzzle è stato ispirato da un codice per un processore a 16 bit che ho dovuto generare di recente)

PS, mi chiedo: per quale numero il programma ottimale è più lungo?


9
Per curiosità, perché è x+=xlegale solo se xè pari? Anche per il programma più breve penso che qualcosa come BFS potrebbe funzionare.
Sp3000,

Dopo essere arrivati ​​al bersaglio, si potrebbe voler andare al prossimo numero di bersaglio; per avere la possibilità di raggiungere qualsiasi obiettivo, uno dei numeri deve essere dispari. Non volevo creare un flusso infinito di obiettivi per evitare la complessità non necessaria, ma lo spirito è quello di consentire un tale flusso ...
Anatolyg,

Ho modificato la restrizione sul numero di passaggi, quindi per il numero di destinazione 0 o 1 non è necessario che il programma di output sia vuoto.
anatolyg

3
se x+=xfunziona solo per pari x, come mai l'esempio per un input di 25 doppie 3?
bcsb1001

Risposte:


2

CJam, 31

Piace la risposta di @Tobia , anche il mio algoritmo viene rubato senza vergogna ispirato alla risposta di @CChak . Ma, esercitando la magia nera che è CJam, sono riuscito a realizzare un'implementazione ancora più piccola dell'algoritmo.

Provalo qui.

golfed:

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.";

Per favore, correggimi se sbaglio, ma credevo che l'operazione modulo 65536 usata nelle risposte con un algoritmo simile non fosse necessaria. Ho interpretato la domanda in modo tale che possiamo supporre che l'ingresso sarà un numero intero a 16 bit senza segno valido e anche tutti i valori intermedi o i risultati di questo algoritmo.


Un punto interessante sulla rimozione della mod 65536. Penso che sia un buon esempio che il "numero predeterminato" debba essere compreso tra 0 e 65535.
CChak,

8

Perl 107 97

Primo post, quindi ecco qui.

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";}}

Che si adatta a tutti i criteri di aggiunta del registro, ma non ho eseguito un controllo esaustivo per vedere se la mia risposta era sempre entro 2n + 2 dal numero ottimale di passaggi. È comunque entro il limite per ogni numero di Fibonacci.

Ecco una ripartizione più dettagliata

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.

Come ho già detto, questo è il mio primo tentativo di giocare a golf, quindi sono sicuro che questo può essere migliorato. Inoltre, non sono sicuro se la chiamata della subroutine iniziale debba essere conteggiata o meno in una chiamata ricorsiva, il che potrebbe farci guadagnare qualche carattere.

È interessante notare che possiamo ridurre il codice di 11 byte * e migliorare la nostra "efficienza" in termini di numero di operazioni di registro, allentando il requisito secondo cui solo i valori pari possono essere "raddoppiati". L'ho incluso per divertimento qui:

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";}}

Inizia addendum:

Mi è davvero piaciuto questo problema, e mi sono armeggiato con esso per le ultime due settimane. Ho pensato di pubblicare i miei risultati.

Alcuni numeri:

Utilizzando un algoritmo BFS per trovare una soluzione ottimale, nei primi 2 ^ 16 numeri ci sono solo 18 numeri che richiedono 23 passaggi. Sono: 58558, 59894, 60110, 61182, 61278, 62295, 62430, 62910, 63422, 63462, 63979, 64230, 64314, 4486, 64510, 64698, 64854, 65295.

Utilizzando l'algoritmo ricorsivo sopra descritto, il numero "più difficile" da raggiungere è 65535, a 45 operazioni. (65534 prende 44 e ci sono 14 numeri che fanno 43 passi) 65535 è anche la più grande partenza da ottimale, 45 vs 22. La differenza di 23 passi è 2n + 1. (Solo tre numeri colpiscono 2n: 65534, 32767, 32751.) Ad eccezione dei casi banali (zero-step), nell'intervallo definito, il metodo ricorsivo fa una media di circa 1,4 volte la soluzione ottimale.

Conclusione: per i numeri 1-2 ^ 16, l'algoritmo ricorsivo non supera mai la soglia definita di 2n + 2, quindi la risposta è valida. Sospetto però che inizierebbe a discostarsi troppo dalla soluzione ottimale per registri più grandi / più bit.

Il codice che ho usato per creare il BFS era sciatto, intensivo di memoria, non commentato e deliberatamente non incluso. Quindi ... non devi fidarti dei miei risultati, ma sono abbastanza fiducioso in loro.


Una soluzione non BFS, eccezionale!
Anatolyg

Penso che anche per i casi più patologici rimarrai entro un fattore 4, forse meglio (poiché conosco solo un limite inferiore per la soluzione ottimale). Che è ancora abbastanza buono.
rationalis,

7

Python 3, 202 byte

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)

(Grazie a @rationalis per alcuni byte)

Ecco una soluzione estremamente semplice. Vorrei poter giocare a golf sull'ultima linea meglio, ma al momento non ho idee. Chiama con S(25).

Il programma esegue semplicemente un semplice BFS senza memorizzazione nella cache, quindi è molto lento. Ecco S(97), per alcuni output di esempio:

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

5

Dyalog APL, 49 caratteri / byte *

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

Algoritmo spudoratamente ispirato alla risposta di @CChak .

Esempio:

    {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 supporta un set di caratteri legacy che ha i simboli APL associati ai valori superiori di 128 byte. Pertanto, un programma APL che utilizza solo caratteri ASCII e simboli APL può essere considerato come byte == caratteri.


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)

Non posso garantire che questo rimanga entro il doppio del programma ottimale per i numeri pari, ma è efficiente. Per tutti gli input validi 0 <= n < 65536, è essenzialmente istantaneo e genera un programma di massimo 33 istruzioni. Per una dimensione di registro arbitraria n(dopo aver corretto quella costante), ci vorrebbe del O(n)tempo con al massimo le 2n+1istruzioni.

Qualche logica binaria

È npossibile raggiungere qualsiasi numero dispari in 31 passaggi: fare y+=x, ottenere x,y = 1,1e quindi continuare a raddoppiare xcon x+=x(per il primo raddoppio x+=y, poichéx è dispari per cominciare). xraggiungerà ogni potenza di 2 in questo modo (è solo uno spostamento a sinistra), e quindi puoi impostare qualsiasi bit di yessere 1 aggiungendo la potenza corrispondente di 2. Dato che stiamo usando registri a 16 bit, e ogni bit tranne perché il primo richiede un raddoppio per raggiungere e unoy+=x da impostare, otteniamo un massimo di 31 operazioni.

Qualsiasi numero pari nè solo una potenza di 2, chiamalo a, moltiplica un numero dispari, chiamalo m; cioè n = 2^a * m, o equivalentemente, n = m << a. Utilizzare il processo sopra per ottenere m, quindi reimpostare xspostandolo a sinistra fino a raggiungere lo 0. Eseguire un x+=ya x = m, quindi continuare a raddoppiarex , la prima volta usando x+=ye successivamente usandox+=x .

Qualunque cosa asia, ci vogliono 16-aturni xper ottenere y=me ulteriori aturni per ripristinare x=0. Altri aspostamenti di xsi verificheranno dopo x=m. Quindi 16+aviene utilizzato un totale di turni. Ci sono fino a 16-abit che devono essere impostati per ottenere m, e ognuno di questi ne prenderà uno y+=x. Finalmente abbiamo bisogno di un passaggio in più quandox=0 impostarlo su m x+=y,. Quindi sono necessari al massimo 33 passaggi per ottenere qualsiasi numero pari.

Naturalmente, puoi generalizzare questo a qualsiasi registro di dimensioni, nel qual caso ci vuole sempre al massimo 2n-1e2n+1 ops per ninteri pari e dispari , rispettivamente.

ottimalità

Questo algoritmo produce un programma quasi ottimale (ovvero entro 2n+2se nè presente il numero minimo di passaggi) per i numeri dispari. Per un dato numero dispari n, se il mth bit è il 1 iniziale, allora qualsiasi programma prende almeno i mpassi per arrivare a , x=no y=n, poiché l'operazione che aumenta più velocemente i valori dei registri è x+=xo y+=y(cioè doppi) e ci vogliono mdoppi per arrivare a il mth bit da 1. Poiché questo algoritmo compie la maggior parte dei 2mpassaggi (al massimo due per il raddoppio, uno per il turno e uno y+=x), qualsiasi numero dispari viene rappresentato quasi in modo ottimale.

I numeri pari non sono altrettanto buoni, poiché utilizzano sempre 16 operazioni per ripristinare xprima di qualsiasi altra cosa e 8, ad esempio, possono essere raggiunti in 5 passaggi.

È interessante notare che l'algoritmo di cui sopra non utilizza mai y+=yaffatto, poiché yè sempre mantenuto strano. In tal caso, potrebbe effettivamente trovare il programma più breve per l'insieme limitato di sole 3 operazioni.

analisi

# 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()

Ho scritto un semplice test per verificare che la mia soluzione produca effettivamente risultati corretti e non superi mai i 33 passaggi, per tutti gli input validi ( 0 <= n < 65536).

Inoltre, ho tentato di fare un'analisi empirica per confrontare l'output della mia soluzione con gli output ottimali, tuttavia risulta che l'ampiezza della ricerca è troppo inefficiente per ottenere la lunghezza minima dell'output per ogni input valido n. Ad esempio, l'utilizzo di BFS per trovare l'output per n = 65535non termina in un periodo di tempo ragionevole. Tuttavia sono uscito bfs()e sono aperto ai suggerimenti.

Ho, tuttavia, testato la mia soluzione contro @ CChak (implementata in Python qui come U). Mi aspettavo che il mio sarebbe andato peggio, dal momento che è drasticamente inefficiente per i numeri pari più piccoli, ma mediata su tutta la gamma in due modi, il mio ha prodotto un output di lunghezza in media dal 10,8% al 12,3% in meno. Pensavo che ciò fosse dovuto a una migliore efficienza della mia soluzione sui numeri dispari, quindi Vusa il mio sui numeri dispari e @ CChak sui numeri pari, ma Vè in mezzo (circa il 10% in meno di U, il 3% in più di S).


1
Molta logica in 201 byte!
Anatolyg

@analtolyg Cosa posso dire, mi piace la matematica e mi armeggio. Potrei studiare altri approcci, poiché la soluzione del numero pari ha margini di miglioramento.
rationalis,

Whoa, non avevo nemmeno realizzato che x,y='xy'fosse possibile fino ad ora. Sfortunatamente, non riesco a pensare a un modo per riscrivere in c*b+e*2modo conciso con la %formattazione.
rationalis,

Ah, non avevo capito che l'hai usato da qualche altra parte. Sono solo io o S(2)l'output è davvero lungo?
Sp3000,

Sfortunatamente, con la mia soluzione, ogni numero pari compie almeno 19 passaggi ( S(2)essendo il più breve a 19). Non tengo traccia di xed yesplicitamente, quindi anche se xraggiunge 2 dopo il secondo passaggio, continua comunque a reimpostare xsu 0. Sento che ci deve essere una soluzione migliore, ma al momento non riesco a pensare a uno.
rationalis,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.