Emette tutti i programmi di arresto (scrivi un interprete parallelo)


26

L'obiettivo di questa sfida è (eventualmente) produrre ogni possibile programma di arresto in una lingua a scelta. All'inizio può sembrare impossibile, ma puoi farlo con una scelta molto attenta dell'ordine di esecuzione.

Di seguito è riportato un diagramma ASCII per illustrare questo. Lascia che le colonne rappresentino una numerazione di ogni possibile programma (ogni programma è un numero finito di simboli da un alfabeto finito). Lascia che ogni riga rappresenti un passo singolare nell'esecuzione di quel programma. Un Xrappresenta l'esecuzione eseguita da quel programma in quel passaggio temporale.

 step#  p1 p2 p3 p4 p5 p6
     1  X  X  X  X  X  X
     2  X  X  X  X  X  
     3  X  X     X  X
     4  X  X     X  X
     5  X  X     X
     6     X     X
     7     X     X
     8     X     X
     9     X     X
     ∞     X     X

Come puoi dire, i programmi 2 e 4 non si fermano. Se dovessi eseguirli uno alla volta, il tuo controller si bloccherebbe nel ciclo infinito che è il programma 2 e non emetterebbe mai programmi 3 e oltre.

Invece, usi un approccio a coda di rondine . Le lettere rappresentano un possibile ordine di esecuzione per i primi 26 passaggi. I *punti sono i luoghi in cui quel programma si è interrotto e viene emesso. Gli .s sono passaggi che non sono stati ancora eseguiti.

 step#  p1 p2 p3 p4 p5 p6
     1  A  C  F  J  N  R  V
     2  B  E  I  M  Q  *  Z
     3  D  H  *  P  U
     4  G  L     T  Y
     5  K  O     X
     6  *  S     .
     7     W     .
     8     .     .
     9     .     .
     ∞     .     .

Requisiti per la lingua di destinazione

La lingua di destinazione (quella interpretata in parallelo) deve essere Turing-complete. A parte questo, può essere qualsiasi lingua completa di Turing, inclusi sottoinsiemi completi di Turing di lingue molto più grandi. Sei anche libero di interpretare cose come le regole di sistema dei tag ciclici. Puoi anche creare una lingua da testare, purché tu possa mostrare perché è Turing completo.

Ad esempio, se si sceglie di testare Brainfuck, è consigliabile testare solo il []-+<>sottoinsieme, poiché l'input non è supportato e l'output viene semplicemente eliminato (vedere di seguito).

Quando si tratta del programma "controller" (che stai giocando a golf), non ci sono requisiti speciali. Si applicano le normali restrizioni linguistiche.

Come creare un elenco infinito di programmi

La maggior parte dei linguaggi di programmazione può essere rappresentata come una serie di simboli da un alfabeto finito. In questo caso, è relativamente facile elencare un elenco di tutti i programmi possibili in ordine crescente. L'alfabeto che usi dovrebbe essere rappresentativo dei requisiti della lingua di destinazione. Nella maggior parte dei casi, questo è ASCII stampabile. Se la tua lingua supporta Unicode come funzionalità aggiuntiva, non dovresti testare tutte le possibili combinazioni di caratteri Unicode, ma solo ASCII. Se la tua lingua utilizza solo []-+<>, non testare le varie combinazioni di caratteri ASCII "comment". Lingue come APL avrebbero i loro alfabeti speciali.

Se la tua lingua è meglio descritta in un modo non alfabetico, come Fractran o Turing Machines, allora ci sono altri metodi ugualmente validi per generare un elenco di tutti i possibili programmi validi.

Interpretazione di un elenco sempre crescente di programmi

La parte fondamentale di questa sfida è quella di scrivere un interprete parallelo per un elenco crescente di programmi. Ci sono alcuni passaggi di base per questo:

  • Aggiungi un numero finito di programmi all'elenco
  • Interpretare ciascun programma nell'elenco individualmente per un periodo di tempo finito. Ciò può essere realizzato eseguendo un passo di istruzione per ciascuno. Salva tutti gli stati.
  • Rimuovere dall'elenco tutti i programmi che terminano / generano errori
  • Emette i programmi * fermamente arrestati
  • Aggiungi altri programmi all'elenco
  • Simula ogni programma a turno, riprendendo l'esecuzione dei programmi più vecchi da dove era stato interrotto
  • Rimuovere dall'elenco tutti i programmi che terminano / generano errori
  • Emette i programmi * fermamente arrestati
  • ripetere

* Dovresti solo emettere programmi che si fermano in modo pulito. Ciò significa che non sono stati generati errori di sintassi o eccezioni non rilevate durante l'esecuzione. Anche i programmi che richiedono input devono essere chiusi senza emetterli. Se un programma produce output, non dovresti terminarlo, basta buttare l'output.

Più regole

  • Non devi generare nuovi thread per contenere i programmi testati, poiché questo scarica il lavoro di parallelizzazione sul sistema operativo host / altro software.
  • Modifica: per chiudere potenziali scappatoie future, non è consentito eval(o alcuna funzione correlata) una parte del codice del programma testato . È possibile eval un codeblock dal codice dell'interprete. (La risposta BF-in-Python è ancora valida in base a queste regole.)
  • Questo è
  • La lingua in cui scrivi l'invio non deve necessariamente essere la stessa lingua che stai testando / producendo.
  • Si dovrebbe supporre che la memoria disponibile sia illimitata.
  • Quando si dimostra la completezza di Turing, è possibile supporre che l'input sia codificato nel programma e che l'output possa essere letto dallo stato interno del programma.
  • Se il tuo programma esce da solo, probabilmente è sbagliato o un poliglotta.

7
Mi ci è voluto troppo tempo per capire perché"If your program outputs itself, it is probably wrong or a polyglot."
trichoplax

1
Possiamo supporre che la memoria disponibile sia illimitata (non penso che sia possibile altrimenti)
KSab

1
@KSab Sì, e altrimenti non è assolutamente possibile.
PhiNotPi

1
Sfida di follow-up ( molto più difficile): uscita di ogni programma senza interruzioni.
Milo Brandt,

1
È accettabile produrre lo stesso programma più di una volta?

Risposte:


9

subleq OISC in Python, 317 269 ​​byte

import collections
g=0
P={}
while 1:
    P[g]=[[0],collections.defaultdict(int,enumerate(list(int(x)for x in reversed(str(g)))))]
    g+=1
    for o,[a,p]in P.items():
        i=a[0]
        p[i+p[i+1]]-=p[i+p[i]]
        if p[i+p[i+1]]<=0:a[0]+=p[i+2]
        else:a[0]+=3
        if a[0]<0:print o;del P[o]

https://esolangs.org/wiki/Subleq

Un programma subleq è un elenco estensibile di numeri interi (p) e un puntatore a istruzioni (i). Questa variante di subleq utilizza l'indirizzamento relativo, che la pagina di discussione wiki suggerisce è necessaria per dare completezza ai valori limitati. Ogni segno di spunta, l'operazione p[i+p[i+1]]-=p[i+p[i]]viene eseguita, quindi i+=p[i+2]se il risultato dell'operazione era <= 0, altrimenti i+=3. Se sono mai negativo, il programma si interrompe.

Questa implementazione verifica ogni programma il cui stato iniziale è composto da numeri interi non negativi a una cifra (0-9) con un puntatore di istruzione iniziale pari a 0.

Output:
21 (which represents the program [1 2 0 0 0 0 0...]
121
161
221
271
351
352
461
462
571
572
681
682
791
792

L'output è invertito, per motivi di golf. Le specifiche sopra potrebbero essere riformulate al contrario, ma non corrisponderebbero al codice utilizzato nell'implementazione, quindi non l'ho descritto in questo modo.

EDIT: Il primo programma che mostra una semplice crescita illimitata è 14283, che diminuisce il valore nella posizione di memoria 6 e scrive uno 0 esplicito (al contrario dello 0 implicito in ogni cella) nella cella negativa successiva, ogni tre tick.


9

Etichetta ciclica bit a bit in CJam, 98 87 84 77 byte

L{Z):Z2b1>_,,1>\f{/([\:~]a2*}+{)~[\({1+(:X+\_0=Xa*+}{0+\1>}?_{]a+}{];p}?}%1}g

Poiché si tratta di un ciclo infinito, non è possibile verificarlo direttamente nell'interprete online. Tuttavia, ecco una versione alternativa che legge il numero di iterazioni da STDIN con cui puoi giocare. Per testare il programma completo, avrai bisogno dell'interprete Java .

BCT è una variante minimalista dei sistemi di tag ciclici . Un programma è definito da due stringhe binarie: un elenco (ciclico) di istruzioni e uno stato iniziale. Per semplificarmi la vita durante la stampa dei programmi, ho definito la mia notazione: ognuna delle stringhe è data come una matrice di numeri interi in stile CJam e l'intero programma è circondato [[...]], ad es.

[[[0 0 1 1] [0 1 1 1 0]]]

Sto anche vietando gli stati iniziali vuoti o gli elenchi di istruzioni vuoti.

Le istruzioni in BCT sono interpretate come segue:

  • Se l'istruzione è 0, rimuovere il bit iniziale dallo stato corrente.
  • Se l'istruzione è 1, leggi un altro bit dall'elenco delle istruzioni, chiamalo X. Se il bit iniziale dello stato corrente è 1, aggiungere Xallo stato corrente, altrimenti non fare nulla.

Se lo stato corrente diventa vuoto, il programma si interrompe.

I primi pochi programmi di arresto sono

[[[0] [0]]]
[[[0] [1]]]
[[[0 0] [0]]]
[[[0] [0 0]]]
[[[0 0] [1]]]
[[[0] [0 1]]]
[[[0 1] [0]]]
[[[0] [1 0]]]
[[[0 1] [1]]]
[[[0] [1 1]]]

Se vuoi vedere di più, controlla la versione nell'interprete online che ho linkato sopra.

Spiegazione

Ecco come funziona il codice. Per tenere traccia della coda di rondine avremo sempre un array nello stack che contiene tutti i programmi. Ogni programma è una coppia di una rappresentazione interna del codice del programma (come [[0 1 0] [1 0]]) così come lo stato corrente del programma. Useremo quest'ultimo per fare il calcolo, ma dovremo ricordare il primo per stampare il programma una volta che si ferma. Questo elenco di programmi viene semplicemente inizializzato su un array vuoto con L.

Il resto del codice è un ciclo infinito {...1}gche prima aggiunge uno o più programmi a questo elenco e calcola un passaggio su ciascun programma. I programmi che si arrestano vengono stampati e rimossi dall'elenco.

Sto enumerando i programmi contando un numero binario. La cifra iniziale viene rimossa per garantire che possiamo ottenere anche tutti i programmi con 0 iniziali. Per ciascuna di queste rappresentazioni binarie troncate, spingo un programma per ogni possibile divisione tra istruzioni e stato iniziale. Ad esempio, se il contatore è attualmente in 42, la sua rappresentazione binaria è 101010. Ci liberiamo di guidare 1e di spingere tutte le divisioni non vuote:

[[[0] [1 0 1 0]]]
[[[0 1] [0 1 0]]]
[[[0 1 0] [1 0]]]
[[[0 1 0 1] [0]]]

Dal momento che non vogliamo istruzioni o stati vuoti, iniziamo il contatore su 4, che dà [[[0] [0]]]. Questa enumerazione viene eseguita dal seguente codice:

Z):Z    e# Push Z (initially 3), increment, and store in Z.
2b1>    e# Convert to base 2, remove initial digit.
_,      e# Duplicate and get the number of bits N.
,1>     e# Turn into a range [1 .. N-1].
\       e# Swap the range and the bit list.
f{      e# Map this block onto the range, copying in the bit list on each iteration.
  /     e#   Split the bit list by the current value of the range.
  (     e#   Slice off the first segment from the split.
  [     
    \:~ e#   Swap with the other segments and flatten those.
  ]     e#   Collect both parts in an array.
  a2*   e#   Make an array that contains the program twice, as the initial state is the
        e#   same as the program itself.
}
+       e# Add all of these new programs to our list on the stack.

Il resto del codice mappa un blocco sull'elenco dei programmi, che esegue un passaggio del calcolo BCT sulla seconda metà di queste coppie e rimuove il programma se si ferma:

)~     e# Remove the second half of the pair and unwrap it.
[      e# We need this to wrap the instructions and current state back in an array
       e# again later.
\(     e# Bring the instruction list to the top and remove the leading bit.
{      e# If it's a 1...
  1+   e#   Append a 1 again (the instructions are cyclic).
  (:X+ e#   Remove the next bit, store it in X and also append it again.
  \_0= e#   Bring the current state to the top, get its first bit.
  Xa*+ e#   Append X if that bit was 1 or nothing otherwise.
}{     e# Else (if it's a 0...)
  0+   e#   Append a 0 again (the instructions are cyclic).
  \1>  e#   Discard the leading bit from the current state.
}?
_      e# Duplicate the current state.
{      e# If it's non-empty...
  ]a+  e#   Wrap instructions and state in an array and add them to the program
       e#   pair again.
}{     e# Else (if it's empty)...
  ];p  e# Discard the instructions and the current state and print the program.
}?

Nizza (+1). Alcuni byte potrebbero essere salvati utilizzando il fatto che BCT è Turing completo anche se limitato all'uso di solo 1 come datastring iniziale (il tuo "stato"). Ad esempio, interpretare ogni numero intero positivo successivo in binario come 1P, quindi eseguire P su 1 ed emettere P se l'esecuzione termina (ricollegandosi di nuovo). (Ovviamente, qualsiasi P che inizia con 0 sarebbe quindi nell'elenco, poiché eliminerebbe immediatamente il datastring iniziale.)
res

8

Brainfuck in Python, 567 byte

Una soluzione relativamente semplice, poiché Brainfuck non è certo la lingua più difficile per cui scrivere un interprete.

Questa implementazione di Brainfuck ha il puntatore dati che inizia da 0, ha permesso di assumere solo un valore positivo (considerato un errore se tenta di andare a sinistra di 0). Le celle di dati possono assumere valori compresi tra 0 e 255 e vanno a capo. Le 5 istruzioni valide sono ><+[]( -non è necessario a causa della confezione).

Penso che l'output sia ora corretto, tuttavia è difficile essere sicuri che stia stampando tutte le possibili soluzioni, quindi potrei mancarne alcune.

o="><+]["
A="[]if b%s1<0else[(p,a+1,b%s1,t+[0],h)]"
C="[(p,h[a]+1,b,t,h)if t[b]%s0else(p,a+1,b,t,h)]"
I=lambda s,i:i*">"if""==s else o[o.find(s[0])+i-5]+I(s[1:],i*o.find(s[0])>3)
s="";l=[]
while 1:
 s=I(s,1)
 r=[];h={}
 for i in range(len(s)):
    if s[i]=="[":r+=[i]
    if s[i]=="]":
     if r==[]:break
     h[r[-1]]=i;h[i]=r[-1];r=r[:-1]
 else:
    if r==[]:
     l+=[(s,0,0,[0],h)];i=0
     while i<len(l):
        p,a,b,t,h=l[i]
        if a>=len(p):print p;l[i:i+1]=[]
        else:l[i:i+1]=eval([A%("+","+"),A%("-","-"),"[(p,a+1,b,t[:b]+[(t[b]+1)%256]+t[b+1:],h)]",C%">",C%"=="][o.find(p[a])]);i+=1

Le prime uscite:

>
+
>>
+>
><
>+
++
[]
>>>
+>>

E un elenco dei primi 2000: http://pastebin.com/KQG8PVJn

E infine un elenco dei primi 2000 output con []in essi: http://pastebin.com/iHWwJprs
(tutto il resto è banale purché sia ​​valido)

Si noti che l'output non è in un ordine ordinato, sebbene possa apparire in questo modo per molti di essi, poiché i programmi che richiedono più tempo verranno stampati in seguito.


1
Sia i nudi [-]che [+]dovrebbero apparire definitivamente perché i contenuti del loop vengono semplicemente saltati (nessun involucro coinvolto).
PhiNotPi

@ Sp3000 The [-]ed [+]era un bug che ora dovrebbe essere corretto e che ho aggiornato con le impostazioni
KSab

1
Perché stai sostenendo .? Non è necessario per un sottoinsieme completo di Turing di BF e l'output deve essere ignorato comunque. Inoltre, poiché stai avvolgendo i valori delle celle, penso che tu abbia bisogno solo di uno di -e +.
Martin Ender,

@ MartinBüttner Mi sembra di aver frainteso la domanda; Non ho letto la parte "sottoinsieme completo turing". Tuttavia, ciò non rende la sfida quasi equivalente alla (maggior parte) delle lingue? Non potresti semplicemente effettuare una sostituzione 1 a 1 con Brainfuck (o forse qualcosa di ancora più semplice), ad esempio il codice c qui: en.wikipedia.org/wiki/Brainfuck#Commands .
KSab il

2
Dai un'occhiata a stackoverflow.com/questions/1053931/… in particolare la voce OISC. Inoltre, esaminare la regola 110 di CA e i sistemi di tag ciclici. C'è molto spazio per scegliere in modo creativo un "linguaggio" completo turing in questa sfida.
Sparr,

5

barre in Python, 640 498 byte

g=2
P={}
while 1:
    b=bin(g)[3:]
    P[b]=[[0],['',''],[b]]
    g+=1
    for d,[a,b,c]in P.items():
        s=c[0]
        if a[0]:
            if s.count(b[0]):s=s.replace(b[0],b[1],1)
            else:a[0]=0
        else:
            if s[0]=='0':
                if len(s)==1:del P[d];continue
                s=s[2:]
            else:
                b[0]=b[1]=''
                a[0]=1
                t=p=0
                while t<2:
                    p+=1
                    if p>=len(s):break
                    if s[p]=='0':
                        if p+1>=len(s):break
                        b[t]+=s[p+1]
                        p+=1
                    else:t+=1
                if t<2:del P[d];continue
        c[0]=s
        if len(s)==0:print d;del P[d]

https://esolangs.org/wiki////

Un programma di barre è una stringa, in questo interprete limitato ai caratteri '/' e '\'. In questa implementazione, / è '1' e \ è '0' per consentire un po 'di golf con l'uso del cestino di Python (x). Quando l'interprete incontra un \, viene emesso il carattere successivo ed entrambi i caratteri vengono rimossi. Quando incontra un /, cerca la ricerca e sostituisce i motivi come / cerca / sostituisci / inclusi i caratteri di escape all'interno dei motivi (\\ rappresenta \ e \ / rappresenta /). Tale operazione di sostituzione viene quindi eseguita ripetutamente sulla stringa fino a quando la stringa di ricerca non è più presente, quindi l'interpretazione continua di nuovo dall'inizio. Il programma si interrompe quando è vuoto. Un programma verrà ucciso se c'è un set non chiuso di / pattern o un \ senza carattere dopo di esso.

Example output and explanations:
01 outputs '1' and halts
00 outputs '0' and halts
0101 outputs '11' and halts
0100 ...
0001
0000
010101
010100
010001
010000 ...
101110 replaces '1' with '', leaving '00', which outputs '0' and halts

4

Treehugger in Java, 1.299 1.257 1.251 1.207 1.203 1.201 1.193 1.189 byte

import java.util.*;class I{static class N{N l,r;byte v;}static class T extends Stack<N>{{push(new N());}void p(){pop();if(size()==0)p();}int i,h;char[]s;}static void s(T t){if(t.i>=t.s.length){t.h=1;return ;}char c=t.s[t.i];if(c=='<'){if(t.peek().l==null)t.peek().l=new N();t.push(t.peek().l);}if(c=='>'){if(t.peek().r==null)t.peek().r=new N();t.push(t.peek().r);}if(c=='^')t.p();if(c=='+')t.peek().v++;if(c=='-')t.peek().v--;if(c=='['&&t.peek().v==0){int i=1;while(i>0){t.i++;if(t.s[t.i]==']')i--;if(t.s[t.i]=='[')i++;}return;}if(c==']'&&t.peek().v!=0){int i=1;while(i>0){t.i--;if(t.s[t.i]==']')i++;if(t.s[t.i]=='[')i--;}return;}t.i++;}static char[]n(char[]a){String b="<^>[+-]";int q=a.length;for(int i=q-1;i>=0;i--){int j=b.indexOf(a[i]);if(j<6){a[i]=b.charAt(j+1);return a;}a[i]='<';}a=Arrays.copyOf(a,q+1);a[q]='<';return a;}public static void main(String[]a){List<T>z=new ArrayList<T>();char[]c={};while(true){T t=new T();t.s=c;if(b(c))z.add(t);c=n(c.clone());for(T u:z)try{s(u);if(u.h>0){z.remove(u);System.out.println(u.s);break;}}catch(Exception e){z.remove(u);break ;}}}static boolean b(char[]c){int i=0;for(char d:c){if(d=='[')i++;if(d==']')i--;if(i<0)return 0>0;}return i==0;}}

4

BrachylogProblema post-corrispondenza , 10 byte

≜;?{~c}ᵐ\d

Provalo online!

Funzione che è un generatore che genera tutti i possibili problemi di corrispondenza post per i quali le soluzioni di forzatura bruta alla fine si arrestano. (È noto che le soluzioni di forzatura bruta al problema della corrispondenza post sono un'operazione completa di Turing.) Il collegamento TIO contiene un'intestazione che converte un generatore in un programma completo e stampa immediatamente ogni output mentre viene generato (quindi, quando TIO uccide il programma a causa del consumo di oltre 60 secondi di tempo di esecuzione, l'output prodotto finora è visibile).

Questo utilizza una formulazione del problema in cui le stringhe vengono fornite come stringhe di cifre, gli zeri iniziali non sono consentiti ad eccezione di 0se stesso, le soluzioni al problema che coinvolgono zero iniziali non sono accettate e una stringa di cifre può essere rappresentata come il numero o meno il numero. Chiaramente, nulla di tutto ciò ha alcun impatto sulla completezza della lingua di Turing (perché non è necessario che un problema di corrispondenza Post utilizzi la cifra zero).

Questo programma funziona generando tutte le possibili soluzioni ai problemi, quindi lavorando all'indietro per trovare i programmi originali da loro risolti. Pertanto, un singolo programma può essere emesso più volte. Non è chiaro se ciò invalidi o meno la risposta; si noti che tutti i programmi di arresto verranno infine emessi almeno una volta (in effetti, infinitamente molte volte, poiché qualsiasi programma che ha soluzioni ha infinitamente molte soluzioni) e i programmi di non arresto non verranno mai emessi.

Spiegazione

≜;?{~c}ᵐ\d
≜           Brute-force all numbers:
 ;?           Pair {the number} with {itself}
   {  }ᵐ      For each pair element:
    ~c          Brute-force a partition of that element into substrings
        \     such that the two elements each have the same number of substrings
        \     and group together corresponding substrings
         d    and remove duplicated pairs {to produce a possible output}

2

"Viola senza I / O" a Ceylon, 662

import ceylon.language{l=variable,I=Integer,m=map,S=String}class M(S d){l value t=m{*d*.hash.indexed};l I a=0;l I b=0;l I i=0;I g(I j)=>t[j]else 0;value f=m{97->{a},98->{b},65->{g(a)},66->{g(b)},105->{i},49->{1}};value s=m{97->((I v)=>a=v),98->((I v)=>b=v),65->((I v)=>t=m{a->v,*t}),66->((I v)=>t=m{b->v,*t}),105->((I v)=>i=v)};I&I(I)x{throw Exception(d);}I h(I v)=>f[v]?.first else x;shared void p(){(s[g(i)]else x)(h(g(i+1))-h(g(i+2)));i+=3;}}shared void run(){value a='!'..'~';{S*}s=expand(loop<{S*}>{""}((g)=>{for(c in a)for(p in g)p+"``c``"}));l{M*}r={};for(p in s){r=[M(p),*r];for(e in r){try{e.p();}catch(x){print(x.message);r=r.filter(not(e.equals));}}}}

Il viola è un linguaggio auto-modificante a una istruzione che è stato chiesto di interpretare qui . Come ingresso e di uscita non sono rilevanti per questo compito, ho rimosso il osignificato del simbolo dall'interprete, in modo tale che le (potenzialmente) simboli validi sono solo a, b, A, B, ie 1(l'ultimo solo per la lettura, non per la scrittura).

Ma poiché Purple si auto-modifica (e usa il suo codice sorgente come dati), potenzialmente anche programmi contenenti altri caratteri sono utili, quindi ho optato per consentire tutti i caratteri ASCII stampabili (non bianchi) nel codice (altri potrebbero essere utile anche, ma non sono così facilmente stampabili).

(È possibile modificare l'interprete in modo che prenda invece la stringa di caratteri consentiti come argomento della riga di comando - cambia la riga commentata che definisce di aseguito. Quindi la lunghezza diventa 686 byte.)

Il mio interprete "parallelo" crea così tutte le stringhe finite da quei caratteri (in lunghezza crescente e ordine lessicografico) e prova ciascuno di essi.

Il viola si arresterà senza errori ogni volta che il comando da leggere dal nastro per l'esecuzione non è valido - quindi non ci sono programmi non validi e molti, molti di quelli di arresto. (La maggior parte si ferma anche al primo passo, solo alcuni dei programmi con lunghezza 3 arrivano al secondo passo (e poi si fermano), i primi senza sosta hanno lunghezza 6.

Penso che sia il primo programma non-stop nell'ordine provato dal mio interprete aaaiaa, che nel primo passaggio imposta il aregistro su 0 (che era già), e il secondo e ogni passaggio successivo riporta il puntatore dell'istruzione su 0, facendolo eseguire di iaanuovo.

Ho riutilizzato parte del codice scritto per il mio interprete di viola "standard" , ma a causa della rimozione di input e output, il mio interprete parallelo diventa anche leggermente più breve di quello, includendo la logica aggiuntiva per l'esecuzione di più programmi contemporaneamente.

Ecco una versione commentata e formattata:

// Find (enumerate) all halting programs in (a non-I/O subset of) Purple.
//
// Question:  /codegolf//q/51273/2338
// My answer: /codegolf//a/65820/2338

// We use a turing-complete subset of the Purple language,
// with input and output (i.e. the `o` command) removed.

import ceylon.language {
    l=variable,
    I=Integer,
    m=map,
    S=String
}

// an interpreting machine.
class M(S d) {
    // The memory tape, as a Map<Integer, Integer>.
    // We can't modify the map itself, but we
    // can replace it by a new map when update is needed.
    l value t = m {
        // It is initialized with the code converted to Integers.
        // We use `.hash` instead of `.integer` because it is shorter.
        *d*.hash.indexed
    };

    // three registers
    l I a = 0;
    l I b = 0;
    l I i = 0;

    // get value from memory
    I g(I j) =>
            t[j] else 0;

    // Map of "functions" for fetching values.
    // We wrap the values in iterable constructors for lazy evaluation
    //  – this is shorter than using (() => ...).
    // The keys are the (Unicode/ASCII) code points of the mapped
    // source code characters.
    value f = m {
        // a
        97 -> { a },
        // b
        98 -> { b },
        // A
        65 -> { g(a) },
        // B
        66 -> { g(b) },
        // i
        105 -> { i },
        // 1
        49 -> { 1 }
    };

    // Map of functions for "storing" results.
    // The values are void functions taking an Integer,
    // the keys are the ASCII/Unicode code points of the corresponding
    // source code characters.
    value s = m {
        // a
        97 -> ((I v) => a = v),
        // b
        98 -> ((I v) => b = v),
        // Modification of the memory works by replacing the map with
        // a new one.
        // This is certainly not runtime-efficient, but shorter than
        // importing ceylon.collections.HashMap.
        // A
        65 -> ((I v) => t = m { a->v, *t }),
        // B
        66 -> ((I v) => t = m { b->v, *t }),
        // i
        105 -> ((I v) => i = v)
    };


    // Exit the interpretation, throwing an exception with the machine's
    // source code as the message.  The return type is effectively `Nothing`,
    // but shorter (and fits the usages).
    I&I(I) x {
        throw Exception(d);
    }

    // accessor function for the f map
    I h(I v) =>
            f[v]?.first else x;

    // a single step
    shared void p() {
        (s[g(i)] else x)(h(g(i + 1)) - h(g(i + 2)));
        i += 3;
    }
}

// the main entry point
shared void run() {
    // the alphabet of "Purple without I/O".
    value a = '!'..'~';
    //// possible alternative to use a command line argument:
    // value a = process.arguments[0] else '!'..'~';

    // an iterable consisting of all programs in length + lexicographic order
    {S*} s =
            // `expand` creates a single iterable (of strings, in this case)
            // from an iterable of iterables (of strings).
             expand(
        // `loop` creates an iterable by applying the given function
        // on the previous item, repeatedly.
        // So here we start with the iterable of length-zero strings,
        // and in each iteration create an iterable of length `n+1` strings
        // by concatenating the length `n` strings with the alphabet members.
        loop<{S*}>{ "" }((g) =>
                {
                    for (c in a)
                        for (p in g)
                            p + "``c``"
                }));

    // This is a (variable) iterable of currently running machines.
    // Initially empty.
    l {M*} r = {};

    // iterate over all programs ...
    for(p in s) {
        // Create a machine with program `p`, include it
        //  in the list of running machines.
        //
        // We use a sequence constructor here instead of
        //  an iterable one (i.e. `r = {M(p, *r)}` to prevent
        // a stack overflow when accessing the deeply nested
        // lazy iterable.
        r = [M(p), *r];
        // iterate over all running machines ...
        for(e in r) {
            try {
                // run a step in machine e.
                e.p();
            } catch(x) {
                // exception means the machine halted.
                // print the program
                print(x.message);
                // remove the machine from the list for further execution
                r = r.filter(not(e.equals));
            }
        }
        // print(r.last);
    }
}

2

Calcolo del combinatore SK in Haskell , 249 byte

data C=H|S|K|C:$C deriving(Eq,Show)
n(a:$b)=m a*n b
n a=m a
m S=1
m K=1
m(S:$a)=n a
m _=0
f H=[S,K,S:$H,K:$H,S:$H:$H]
f a=[S:$b:$c:$d|b:$d:$(c:$e)<-[a],d==e,n b*n c*n d>0]++[K:$a:$H|n a>0]++do b:$c<-[a];[d:$c|d<-f b]++[b:$d|n b>0,d<-f c]
l=H:(f=<<l)

Provalo online!

Come funziona

Le regole di valutazione call-by-value per il calcolo del combinatore SK sono le seguenti:

(a) S xyzxz ( yz ), per x , y , z in forma normale;
(b) K xyx , per x , y in forma normale;
(c) xyxy , se xx ′;
(d) xyxy ′, per x in forma normale, se yy ′ .

Poiché siamo interessati solo ad arrestare il comportamento, espandiamo leggermente la lingua introducendo un simbolo H che non è in forma normale, ma a cui "valutano" tutte le forme normali:

(a) S xyzxz ( yz ), per x , y , z in forma normale;
(b ′) K x H ↦ x , per x in forma normale;
(c) xyxy , se xx ′;
(d) xyxy ′, per x in forma normale, se yy ′ ;
(e) S ↦ H;
(f) K ↦ H;
(g) SH ↦ H;
(h) KH ↦ H;
(i) SHH ↦ H.

Consideriamo qualsiasi applicazione H x come un errore di runtime da trattare come se fosse un ciclo infinito e ordiniamo valutazioni in modo tale che nessuna H sia prodotta da (e) - (i) tranne in un contesto in cui sarà ignorato (livello superiore, qualsiasi K x ☐ , qualsiasi K ignorato, qualsiasi S x ☐ ignorato per x in forma normale, qualsiasi S☐H ignorato). In questo modo non influiamo sul comportamento di arresto dei soliti termini privi di H.

I vantaggi di queste regole modificate sono che ogni termine normalizzabile ha un percorso di valutazione unico per H e che ogni termine ha un numero finito di possibili preimmagini in under. Quindi, invece di utilizzare l'approccio a coda di rondine, possiamo fare una ricerca molto più ampia e più ampia di tutti i percorsi di valutazione inversa di H.

ncontrolla se un termine è in forma normale, ftrova tutte le possibili preimmagini di un termine ed lè un pigro elenco infinito di termini normalizzabili prodotti dall'ampiezza della ricerca di H.

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.