Risolvi il problema dell'arresto per SNISP modellare


10

Nello spirito di Solve the Halting Problem for Befinge , definiamo un altro linguaggio 2D chiamato Modilar SNISP . Modilar SNISP ha le seguenti sei istruzioni:

  • \ dirige il puntatore all'istruzione come segue:
    • se avvicinato dall'alto, vai a destra;
    • se avvicinato da destra, vai su;
    • se avvicinato dal basso, vai a sinistra;
    • se avvicinato da sinistra, scendi.
  • / dirige il puntatore all'istruzione come segue:
    • se avvicinato dall'alto, vai a sinistra;
    • se avvicinato da sinistra, vai su;
    • se avvicinato dal basso, vai a destra;
    • se avvicinato da destra, scendi.
  • ! salta la prossima istruzione.
  • @ inserisce la posizione e la direzione IP nello stack di chiamate.
  • #estrae una posizione IP e una direzione dallo stack di chiamate e li ripristina, quindi salta sull'istruzione successiva. Se lo stack di chiamate è vuoto, l'esecuzione si interrompe.
  • . non fa nulla.

Il puntatore dell'istruzione inizia nell'angolo in alto a sinistra andando a destra. Se mai lascia il campo di gioco, l'esecuzione si interrompe.

Il SNISP moderno non può essere più potente di un PDA , poiché la sua unica fonte di archiviazione illimitata è uno stack (lo stack di chiamate) con un alfabeto finito (l'insieme di tutte le coppie IP (posizione, direzione)). Il problema dell'arresto è decidibile per i PDA , quindi questa sfida dovrebbe essere sempre possibile.

La sfida

Il tuo obiettivo è scrivere un programma che accetta una matrice di caratteri che rappresentano un programma SNISP Modilar e restituisce uno dei due output distinti a seconda che si fermi o meno.

Questo è , quindi vince il programma valido più breve (misurato in byte ).

specificazioni

  • Il modo in cui prendi una matrice di caratteri è flessibile: sono accettabili una stringa separata da una nuova riga, una matrice di stringhe, una matrice di matrici di caratteri, una seconda matrice di caratteri, una matrice piatta di caratteri con un numero intero che rappresenta la larghezza, ecc. I casi di test optano per la prima di queste scelte.
  • Puoi presumere che la matrice di input sia rettangolare (quindi non devi riempire le righe corte) e sarà di lunghezza e larghezza diverse da zero.
  • Puoi scegliere due uscite distinte, non solo verità / falsità.
  • Si può assumere che la matrice di ingresso consisterà solo comandi validi ( \, /, !, @, #, e .).
  • Quando un comando dice "salta la prossima istruzione", puoi presumere che ci sarà una prossima istruzione da saltare. In particolare, non si incontrerà mai in circostanze in cui (1) si trova sul bordo del campo di gioco e (2) l'IP si sta muovendo perpendicolarmente a quel bordo, in modo tale che le "istruzioni successive" dopo si troverebbero al di fuori del campo di gioco.

Casi test

Il seguente frammento può essere utilizzato per testare programmi nella lingua. Si noti che è leggermente più permissivo rispetto alle specifiche effettive fornite qui (ad esempio, consente caratteri diversi da .quelli non consentiti ).

function htmlEscape(t){let i=document.createElement("span");return i.innerText=t,i.innerHTML}function tick(){snisp.tick(),snisp.update()}function run(){runButton.style.display="none",stopButton.style.display="",code.style.display="none",executionArea.style.display="",snisp.initialize(),intervalId=setInterval(tick,INTERVAL_MS)}function stop(){runButton.style.display="",stopButton.style.display="none",code.style.display="",executionArea.style.display="none",clearInterval(intervalId)}let TICKS_PER_SECOND=5,INTERVAL_MS=1e3/TICKS_PER_SECOND,runButton=document.getElementById("run-button"),stopButton=document.getElementById("stop-button"),code=document.getElementById("code"),executionArea=document.getElementById("execution-display"),intervalId,snisp={x:null,y:null,direction:null,callStack:null,stopped:null,playfield:null,padRows:function(){let t=Math.max(...this.playfield.map(t=>t.length));for(let i=0;i<this.playfield.length;i++)this.playfield[i]=this.playfield[i].padEnd(t,".")},initialize:function(){this.x=0,this.y=0,this.direction="right",this.callStack=[],this.stopped=!1,this.playfield=code.value.split("\n"),this.padRows(),this.update()},getCurrentChar:function(){let t=this.playfield[this.y];if(void 0!=t)return t[this.x]},backslashMirror:function(){let t={up:"left",right:"down",down:"right",left:"up"};this.direction=t[this.direction]},slashMirror:function(){let t={up:"right",right:"up",down:"left",left:"down"};this.direction=t[this.direction]},forward:function(){switch(this.direction){case"up":this.y-=1;break;case"down":this.y+=1;break;case"left":this.x-=1;break;case"right":this.x+=1;break;default:throw"direction is invalid"}},pushState:function(){this.callStack.push({x:this.x,y:this.y,direction:this.direction})},restoreState:function(){let t=this.callStack.pop();void 0!=t?(this.x=t.x,this.y=t.y,this.direction=t.direction):this.stopped=!0},tick:function(){if(this.stopped)return;let t=this.getCurrentChar();if(void 0!=t){switch(t){case"\\":this.backslashMirror();break;case"/":this.slashMirror();break;case"!":this.forward();break;case"@":this.pushState();break;case"#":this.restoreState(),this.forward()}this.forward()}else this.stopped=!0},generatePlayfieldHTML:function(t,i){let e=[];for(let n=0;n<this.playfield.length;n++){let s=[],l=this.playfield[n];for(let e=0;e<l.length;e++){let a=htmlEscape(l[e]);e==t&&n==i&&(a='<span class="highlight">'+a+"</span>"),s.push(a)}e.push(s.join(""))}return e.join("<br>")},update:function(){let t=[];for(let i=0;i<this.callStack.length;i++){let e=this.callStack[i];t.push(this.generatePlayfieldHTML(e.x,e.y))}t.push(this.generatePlayfieldHTML(this.x,this.y));let i=t.join("<br><br>");executionArea.innerHTML=i}};
#code{font-family:monospace;}#execution-display{font-family:monospace;white-space:pre;}.highlight{background-color:yellow;}
<b>Code:</b><br/><textarea id="code" width="300" height="300"></textarea><br/><button id="run-button" onclick="run()">Run</button><button id="stop-button" onclick="stop()" style="display: none;">Stop</button><br/><div id="execution-display"></div>

La forma non golfata può essere trovata qui .

Esitante

.

Il più piccolo programma possibile. Esce a destra.


\\
\/

Si snoda attorno al programma e esce in cima.


.\./.\
.\!/./

Va in loop. Si snoda attraverso parte della pista in due direzioni diverse.


@\!/#
.\@/#

Utilizza tutti e sei i comandi.


@.@.@.@.@.@.@.@.@.#

Il tempo di esecuzione di questo programma è esponenziale nel numero di ripetizioni di @., ma si ferma ancora.


Non-Arrestare

!/\
.\/

Credo che questo sia il ciclo infinito più breve.


@!\\#/@\!\
//@//.#./.
.\#.!\./\.
#.\!@!\@//
/..@.@\/#!
\.@.#.\/@.

Questo si snoda intorno alla pista, generando occasionalmente frame di stack, prima di essere catturato in un ciclo generando infinitamente frame di stack. Non tutti i comandi sono effettivamente utilizzati.

.!/@.@.@.@.@.\
/.@.@.@.@.@.@/
\@.@.@.@.@.@.\
/.@.@.@.@.@.@/
.@\@.@.@.@.@.\
\.@.@.@.@.@.@/

Continua a creare frame di stack, ma nessuno di essi ritorna mai.


Sandbox (ora cancellato)
Esolanging Fruit

La lingua mi ricorda una Fissione molto semplificata .
Sundar - Ripristina Monica il

1
@sundar È un sottoinsieme di SNUSP modulare , allo stesso modo in cui Befinge è (in un certo senso) un sottoinsieme di Befunge.
Esolanging Fruit,

Risposte:


4

Python 3 , 639 byte 630 byte 593 byte

def e(I):
 m=[(0,-1),(0,1),(1,1),(1,-1)];a=lambda i:(tuple(i[0]),i[1]);b=lambda s,q:s.s==q.s and s.S&q.S==q.S
 class O():i=[[0,0],2];S=[];A={}
 def z():d=m[O.i[1]];O.i[0][d[0]]+=d[1]
 def y():O.i=O.S.pop();z()
 def x():O.i[1]=[3,2,1,0][O.i[1]]
 def w():O.i[1]=[2,3,0,1][O.i[1]]
 def v():O.S+=[[O.i[0][:],O.i[1]]]
 while 1:
  p=O();p.s=a(O.i);p.S={a(i)for i in O.S};l=O.A.setdefault(p.s,[]);c=any((b(p,s)for s in l));l+=[p];e=O.i[0];d=not((0<=e[0]<len(I))and(0<=e[1]<len(I[0])))or((x,w,z,v,lambda:len(O.S)==0 or y(),lambda:0)["\\/!@#.".find(I[e[0]][e[1]])]()==1);z()
  if d!=c:return not c or d

Provalo online!

Sento che questo è più una fonte minimizzata che il golf ... Sono sicuro che c'è un modo migliore per arrivarci.

Il programma funziona come un interprete completo per la lingua. Si ferma quando:

  1. Usciamo dal programma
  2. Rileviamo di essere in loop.

Il rilevamento del loop è alquanto ingenuo (e la memoria è pesante). Prima di valutare ogni mossa, memorizziamo nella cache la nostra direzione, posizione e stack correnti. Se vediamo che siamo arrivati ​​a una posizione in cui siamo stati prima, muovendo la stessa direzione, e il nostro stack attuale è un super set di uno stack precedente in questa posizione + direzione, allora sappiamo che siamo in un loop e lo stack sta crescendo (o rimane costante).

Modifica 1 - Grazie a Herman L per aver tagliato il "passaggio". Taglia anche "Vero".

Modifica 2: Lambda-ified alcune funzioni. Numero ridotto di resi. Restituisce "Vero" per la terminazione e "Falso" per la non terminazione. Sfrutta la classe O esistente come oggetto di tracciamento, eliminando la necessità della classe N.


La sostituzione class N():passcon class N():0e def t():passcon def t():0sembra funzionare
Herman L

È possibile passare da una funzione a un programma completo sostituendolo def e(I)con I=input(). Ciò consente di rimuovere tutti i rientri. Le return xdichiarazioni possono essere sostituite con exit(x).
Anfibologico,

Inoltre def u():return len(O.S)==0 or y()potrebbe diventare u=lambda:len(O.S)==0or y(). PS bella soluzione!
Anfibologico,

1

JavaScript (ES6), 258 254 byte

p=>(d=>{for(x=y=r=k=1,s=[],v={};w=[--x,--y,d],c=1<<"\\!@/#".indexOf(q=(p[y]||0)[x]),q&&r&&(e=v[w]?v[w].some(u=>!s.some(t=>u+0==t+0)):1);x+=d>>2,y+=d&3)v[w]=[...s],k=k?c&9?d=c&1?d/4|4*d&12:(d+5)%10:c&4?s.push(w):c&16?(r=s.pop())&&!([x,y,d]=r):c-2:1})(9)|e

Si aspetta un programma non vuoto come una matrice di stringhe, in cui ogni elemento rappresenta una linea di SNISP Modilar. Emette 1se il programma specificato si arresta e in caso 0contrario.

Stessa logica della risposta di @ machina.widmo . Alcuni tentativi falliti di metodi alternativi mi hanno portato a concludere che avrebbero comunque prodotto codice più lungo!

Provalo online!

Spiegazione

Simile all'altra risposta, questa funzione termina con:

  • 1 se il programma si interrompe (ad es. l'IP si sposta dalla griglia o viene spuntato uno stack vuoto)
  • 0se l'IP raggiunge una posizione che ha già visitato, spostandosi nella stessa direzione e con un superset dello stack presente nella visita precedente.

Perché la stessa direzione?

 1
!\/

Il programma sopra si interrompe, ma colpisce la stessa posizione (carattere 1), con uno stack identico, ma da una direzione diversa.

Perché un superset e non solo una dimensione dello stack?

  ab4
!/@@.\
.\..#/

Anche questo si ferma e l'IP colpisce il carattere 4 da una direzione coerente quattro volte, con i seguenti stati di stack ( *indica la parte superiore dello stack):

  • dimensione = 2 [a, b] *
  • dimensione = 1 [a] *
  • dimensione = 1 [b] *
  • size = 0 [] *

Come funziona l'interprete

Le istruzioni ( q) sono tradotte in binario ( c) come segue (con tutti gli altri caratteri, .o altrimenti, fungendo da nops):

1 2 4 8 16
\ ! @ / #

La direzione ( d) è rappresentata come un campo bit:

9 -> right : 1001
1 -> left  : 0001
6 -> down  : 0110
4 -> up    : 0100

Gli specchi ( \/) trasformano la direzione:

\: 6-> 9 9-> 6 4-> 1 1-> 4

d/4 | 4*d&12

/: 1-> 6 6-> 1 4-> 9 9-> 4

(d+5) % 10

Le nuove direzioni trasformano la posizione:

x += d>>2 - 1

y += d&3 - 1

Altre variabili globali

  • x, y: Posizione IP
  • r: contiene il valore estratto dalla pila
  • k: falsa se salta la prossima istruzione (es. da !#)
  • s: stack
  • v: memorizza le posizioni visitate, direzione, istantanea dello stack
  • w: [x, y, d]il valore memorizzato nello stack e utilizzato come valore chiave perv
  • e: falsa se il programma non si interrompe a causa di una corrispondenza cache

Ungolfed

p => (d => {                                                  // set initial direction and avoid a verbose `return` statement
    for (
        x = y = r = k = 1,
        s = [],
        v = {};
        w = [--x, --y, d],                                    // decrement positions early so that x,y 
                                                              // do not require a separate assignment to 0
        c = 1 << "\\!@/#".indexOf(q = (p[y]||0)[x]),          // taking an index of undefined produces an error; using 0 does not
        q && r && (
            e = v[w]
                ? v[w].some(u => !s.some(t => u+0 == t+0))    // in order to compare two arrays, must coerce to strings
                : 1
        );
        x += d>>2,
        y += d&3
    )
        v[w] = [...s],                         // clone stack
        k = k
            ?
                c&9                            // if \ or /
                    ? d = c&1
                        ? d/4 | 4*d&12
                        : (d+5) % 10
                : c&4                          // if @
                    ? s.push(w)
                : c&16                         // if #
                    ? (r = s.pop())
                        && !([x, y, d] = r)    // destructure value in stack if any exists
                : c-2                          // 0 if !
            : 1
})(9) | e
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.