Vi presento, il primo 3% di un auto-interprete Hexagony ...
|./...\..._..>}{<$}=<;>'<..../;<_'\{*46\..8._~;/;{{;<..|M..'{.>{{=.<.).|.."~....._.>(=</.\=\'$/}{<}.\../>../..._>../_....@/{$|....>...</..~\.>,<$/'";{}({/>-'(<\=&\><${~-"~<$)<....'.>=&'*){=&')&}\'\'2"'23}}_}&<_3.>.'*)'-<>{=/{\*={(&)'){\$<....={\>}}}\&32'-<=._.)}=)+'_+'&<
Provalo online! Puoi anche eseguirlo su se stesso, ma ci vorranno circa 5-10 secondi.
In linea di principio, questo potrebbe adattarsi alla lunghezza del lato 9 (per un punteggio di 217 o meno), perché utilizza solo 201 comandi e la versione non modificata che ho scritto per prima (sul lato 30) ha richiesto solo 178 comandi. Tuttavia, sono abbastanza sicuro che ci vorrebbe un'eternità per rendere davvero tutto adatto, quindi non sono sicuro che ci proverò davvero.
Dovrebbe anche essere possibile giocare a golf un po 'nella dimensione 10 evitando l'uso dell'ultima o due file, in modo che le no-op finali possano essere omesse, ma ciò richiederebbe una sostanziale riscrittura, come uno dei primi percorsi join utilizza l'angolo in basso a sinistra.
Spiegazione
Iniziamo spiegando il codice e annotando i percorsi del flusso di controllo:
È ancora piuttosto disordinato, quindi ecco lo stesso diagramma per il codice "non golfato" che ho scritto per primo (in effetti, questo è un lato 20 e in origine ho scritto il codice sul lato 30 ma era così scarso che non avrebbe non migliorare affatto la leggibilità, quindi l'ho compattato un po 'per rendere le dimensioni un po' più ragionevoli):
Clicca per una versione più grande.
I colori sono esattamente gli stessi, a parte alcuni dettagli molto minori, anche i comandi non control-flow sono esattamente gli stessi. Quindi spiegherò come funziona basato sulla versione non golfata, e se vuoi davvero sapere come funziona quella golfizzata, puoi controllare quali parti lì corrispondono a quali nell'esagono più grande. (L'unico problema è che il codice golfizzato inizia con uno specchio in modo che il codice effettivo inizi nell'angolo destro andando a sinistra.)
L'algoritmo di base è quasi identico alla mia risposta CJam . Ci sono due differenze:
- Invece di risolvere l'equazione del numero esagonale centrato, computo solo numeri esagonali centrati consecutivi fino a quando uno è uguale o maggiore della lunghezza dell'input. Questo perché Hexagony non ha un modo semplice per calcolare una radice quadrata.
- Invece di riempire subito l'input con no-ops, controllo più tardi se ho già esaurito i comandi nell'input e stampo un
.
invece se ho.
Ciò significa che l'idea di base si riduce a:
- Leggere e memorizzare la stringa di input mentre ne calcola la lunghezza.
- Trova la più piccola lunghezza laterale
N
(e il corrispondente numero esagonale centrato hex(N)
) che può contenere l'intero input.
- Calcola il diametro
2N-1
.
- Per ogni riga, calcola il rientro e il numero di celle (che si sommano a
2N-1
). Stampa il rientro, stampare le celle (usando .
se l'ingresso è già esaurita), stampare un avanzamento riga.
Nota che ci sono solo no-op, quindi il codice effettivo inizia nell'angolo sinistro (il $
, che salta sopra il >
, quindi iniziamo davvero sul ,
nel percorso grigio scuro).
Ecco la griglia di memoria iniziale:
Quindi il puntatore della memoria inizia sull'input etichettato edge , puntando verso nord. ,
legge un byte da STDIN o un -1
se abbiamo colpito EOF in quel bordo. Quindi, il <
dopo è una condizione per aver letto tutti gli input. Rimaniamo nel circuito di input per ora. Il prossimo codice che eseguiamo è
{&32'-
Questo scrive un 32 nello spazio etichettato sul bordo e quindi lo sottrae dal valore di input nel diff etichettato sul bordo . Nota che questo non può mai essere negativo perché siamo certi che l'input contiene solo ASCII stampabile. Sarà zero quando l'ingresso era uno spazio. (Come sottolinea Timwi, funzionerebbe comunque se l'input potesse contenere avanzamenti di riga o schede, ma eliminerebbe anche tutti gli altri caratteri non stampabili con codici di caratteri inferiori a 32.) In tal caso, il <
puntatore dell'istruzione (IP) viene lasciato verso sinistra e viene preso il percorso grigio chiaro. Quel percorso reimposta semplicemente la posizione del MP con {=
e quindi legge il carattere successivo, quindi gli spazi vengono saltati. Altrimenti, se il personaggio non era uno spazio, eseguiamo
=}}})&'+'+)=}
Questo prima si sposta attorno all'esagono attraverso il bordo della lunghezza fino a quando non è opposto al bordo del diff , con =}}}
. Poi copia il valore dal fronte alla lunghezza spigolo nella lunghezza bordo, e l'incrementa con )&'+'+)
. Vedremo tra un secondo perché questo ha senso. Infine, spostiamo un nuovo vantaggio con =}
:
(I valori dei bordi particolari sono tratti dall'ultimo caso di test fornito nella sfida.) A questo punto, il ciclo si ripete, ma con tutto spostato di un esagono a nord-est. Quindi dopo aver letto un altro personaggio, otteniamo questo:
Ora puoi vedere che stiamo gradualmente scrivendo l'input (meno spazi) lungo la diagonale nord-est, con i caratteri su ogni altro bordo e la lunghezza fino a quel carattere che viene memorizzata parallelamente alla lunghezza etichettata del bordo .
Quando avremo finito con il loop di input, la memoria sarà simile a questa (dove ho già etichettato alcuni nuovi bordi per la parte successiva):
L' %
è l'ultimo carattere si legge, il 29
è il numero di caratteri non-spazio che leggiamo. Ora vogliamo trovare la lunghezza laterale dell'esagono. Innanzitutto, esiste un codice di inizializzazione lineare nel percorso verde scuro / grigio:
=&''3{
Qui, =&
copia la lunghezza (29 nel nostro esempio) nella lunghezza con etichetta del bordo . Quindi si ''3
sposta sul bordo etichettato 3 e ne imposta il valore 3
(di cui abbiamo solo bisogno come costante nel calcolo). Alla fine si {
sposta sul bordo etichettato N (N-1) .
Ora entriamo nel ciclo blu. Questo ciclo incrementa N
(memorizzato nella cella etichettata N ) quindi calcola il suo numero esagonale centrato e lo sottrae dalla lunghezza di input. Il codice lineare che lo fa è:
{)')&({=*'*)'-
Qui, {)
si sposta e incrementi N . ')&(
si sposta sul bordo etichettato N-1 , copia N
lì e lo decrementa. {=*
calcola il loro prodotto in N (N-1) . '*)
moltiplica quello per la costante 3
e incrementa il risultato nell'esagono etichettato con il bordo (N) . Come previsto, questo è l'ennesimo numero esagonale centrato. '-
Calcola infine la differenza tra quella e la lunghezza dell'input. Se il risultato è positivo, la lunghezza del lato non è ancora abbastanza grande e il loop si ripete (dove }}
sposta la MP di nuovo sul bordo etichettato N (N-1) ).
Una volta che la lunghezza del lato è abbastanza grande, la differenza sarà zero o negativa e otteniamo questo:
Prima di tutto, ora esiste il percorso verde lineare molto lungo che esegue l'inizializzazione necessaria per il circuito di uscita:
{=&}}}32'"2'=&'*){=&')&}}
Le {=&
inizia copiando il risultato nel diff bordo nella lunghezza di bordo, perché abbiamo seguito c'è bisogno di qualcosa di non positivo. }}}32
scrive un 32 nello spazio contrassegnato dal bordo . '"2
scrive una costante 2 nel bordo senza etichetta sopra diff . '=&
copia N-1
nel secondo bordo con la stessa etichetta. '*)
lo moltiplica per 2 e lo incrementa in modo da ottenere il valore corretto nel bordo etichettato 2N-1 in alto. Questo è il diametro dell'esagono. {=&')&
copia il diametro nell'altro bordo etichettato 2N-1 . Infine, }}
torna al bordo etichettato 2N-1 in alto.
Etichettiamo nuovamente i bordi:
Il bordo su cui ci troviamo attualmente (che contiene ancora il diametro dell'esagono) verrà utilizzato per scorrere le linee dell'output. Il rientro contrassegnato dal bordo calcolerà quanti spazi sono necessari sulla linea corrente. Le celle con etichetta sul bordo verranno utilizzate per scorrere il numero di celle nella riga corrente.
Ora siamo sulla strada rosa che calcola il rientro . ('-
decrementa l' iteratore di linee e lo sottrae da N-1 (nel bordo del rientro ). Il breve ramo blu / grigio nel codice calcola semplicemente il modulo del risultato ( ~
nega il valore se è negativo o zero e nulla accade se è positivo). Il resto del percorso rosa è quello "-~{
che sottrae il rientro dal diametro al bordo delle celle e quindi si sposta indietro al bordo del rientro .
Il sentiero giallo sporco ora stampa il rientro. I contenuti del loop sono davvero giusti
'";{}(
Dove si '"
sposta sul bordo dello spazio , lo ;
stampa, {}
torna al rientro e lo (
diminuisce.
Quando abbiamo finito, il (secondo) percorso grigio scuro cerca il personaggio successivo da stampare. Si =}
sposta in posizione (il che significa, sul bordo delle celle , rivolto a sud). Quindi abbiamo un ciclo molto stretto di {}
cui si sposta semplicemente verso il basso di due bordi nella direzione sud-ovest, fino a quando non colpiamo la fine della stringa memorizzata:
Notate che ho rietichettato un vantaggio lì EOF? . Una volta che abbiamo elaborato questo personaggio, renderemo quel bordo negativo, in modo che il {}
ciclo finisca qui invece della successiva iterazione:
Nel codice, siamo alla fine del percorso grigio scuro, dove si '
sposta indietro di un passo sul carattere di input. Se la situazione è uno degli ultimi due diagrammi (ovvero c'è ancora un carattere dall'input che non abbiamo ancora stampato), allora stiamo prendendo il percorso verde (quello in basso, per le persone che non sono brave con il verde e blu). Quello è abbastanza semplice: ;
stampa il personaggio stesso. '
si sposta sul bordo dello spazio corrispondente che contiene ancora un 32 di prima e ;
stampa quello spazio. Quindi {~
rende il nostro EOF? negativo per la successiva iterazione, '
sposta un passo indietro in modo da poter tornare all'estremità nord-occidentale della stringa con un altro }{
loop stretto . Che termina sulla lunghezzacella (quella non positiva al di sotto dell'esagono (N) . Infine, }
torna indietro al bordo delle celle .
Se abbiamo già esaurito l'input, allora il loop che cerca EOF? terminerà effettivamente qui:
In tal caso, ci '
spostiamo sulla cella della lunghezza e stiamo invece prendendo il percorso azzurro (in alto), che stampa un no-op. Il codice in questo ramo è lineare:
{*46;{{;{{=
La {*46;
scrive un 46 nel bordo etichettato no-op e lo stampa (cioè un periodo). Poi {{;
si muove alla spazio bordo e stampe che. I {{=
torna al cellule bordi per la successiva iterazione.
A questo punto i percorsi si ricongiungono e (
diminuiscono il bordo delle celle . Se l'iteratore non è ancora zero, prenderemo il percorso grigio chiaro, che inverte semplicemente la direzione del MP con =
e quindi cerca il personaggio successivo da stampare.
Altrimenti, abbiamo raggiunto la fine della riga corrente e l'IP prenderà invece il percorso viola. Ecco come appare la griglia di memoria a quel punto:
Il percorso viola contiene questo:
=M8;~'"=
Il =
inverte la direzione della MP di nuovo. M8
imposta il valore su 778
(perché il codice carattere di M
is 77
e cifre si aggiungeranno al valore corrente). Questo accade 10 (mod 256)
, quindi quando lo stampiamo con ;
, otteniamo un avanzamento di riga. Quindi ~
rende nuovamente il bordo negativo, '"
torna al bordo delle linee e =
inverte di nuovo il MP.
Ora se il bordo delle linee è zero, abbiamo finito. L'IP prenderà il percorso (molto breve) rosso, dove @
termina il programma. Altrimenti, continuiamo sul sentiero viola che ritorna a quello rosa, per stampare un'altra linea.
Diagrammi di flusso di controllo creati con HexagonyColorer di Timwi . Diagrammi di memoria creati con il debugger visivo nel suo IDE esoterico .
abc`defg
diventerebbe effettivamente pastebin.com/ZrdJmHiR