Mini-Flak, 6851113 cicli
Il programma (letteralmente)
So che molte persone probabilmente non si aspettano che un quine Mini-Flak utilizzi caratteri non stampabili e persino caratteri multi-byte (rendendo rilevante la codifica). Tuttavia, questo quine fa e gli non stampabili, combinati con la dimensione del quine (93919 caratteri codificati come 102646 byte di UTF-8), rendono abbastanza difficile inserire il programma in questo post.
Tuttavia, il programma è molto ripetitivo e, come tale, si comprime molto bene. In modo che l'intero programma sia disponibile letteralmente da Stack Exchange, c'è un xxd
hexdump reversibile di una gzip
versione compressa del quine completo nascosto dietro il pieghevole di seguito:
00000000: 1f8b 0808 bea3 045c 0203 7175 696e 652e .......\..quine.
00000010: 6d69 6e69 666c 616b 00ed d9db 6a13 4118 miniflak....j.A.
00000020: 0060 2f8b f808 0d64 a1c1 1dc8 4202 c973 .`/....d....B..s
00000030: 4829 4524 0409 22e2 5529 a194 1242 1129 H)E$..".U)...B.)
00000040: d2d7 ca93 f9cf 4c4c d45b 9536 e6db 6967 ......LL.[.6..ig
00000050: 770e 3bc9 ffed eca9 edb7 b1a4 9ad2 6a1d w.;...........j.
00000060: bfab 75db c6c6 6c5f 3d4f a5a6 8da6 dcd8 ..u...l_=O......
00000070: 465b d4a5 5a28 4bd9 719d 727b aa79 f9c9 F[..Z(K.q.r{.y..
00000080: 43b6 b9d7 8b17 cd45 7f79 d3f4 fb65 7519 C......E.y...eu.
00000090: 59ac 9a65 bfdf 8f86 e6b2 69a2 bc5c 4675 Y..e......i..\Fu
000000a0: d4e4 bcd9 5637 17b9 7099 9b73 7dd3 fcb2 ....V7..p..s}...
000000b0: 4773 b9bc e9bd b9ba 3eed 9df7 aeaf 229d Gs......>.....".
000000c0: e6ed 5eae 3aef 9d46 21b2 5e4d bd28 942e ..^.:..F!.^M.(..
000000d0: 6917 d71f a6bf 348c 819f 6260 dfd9 77fe i.....4...b`..w.
000000e0: df86 3e84 74e4 e19b b70e 9af0 111c fa0d ..>.t...........
000000f0: d29c 75ab 21e3 71d7 77f6 9d8f f902 6db2 ..u.!.q.w.....m.
00000100: b8e1 0adf e9e0 9009 1f81 f011 18d8 1b33 ...............3
00000110: 72af 762e aac2 4760 6003 1bd8 698c c043 r.v...G``...i..C
00000120: 8879 6bde 9245 207c 04ae 5ce6 2d02 e1bb .yk..E |..\.-...
00000130: 7291 4540 57f8 fe0d 6546 f89b a70b 8da9 r.E@W...eF......
00000140: f5e7 03ff 8b8f 3ad6 a367 d60b f980 679d ......:..g....g.
00000150: d3d6 1c16 f2ff a767 e608 57c8 c27d c697 .......g..W..}..
00000160: 4207 c140 9e47 9d57 2e50 6e8e c215 b270 B..@.G.W.Pn....p
00000170: bdf6 9926 9e47 9d05 ce02 0ff0 5ea7 109a ...&.G......^...
00000180: 8ba6 b5db 880b 970b 9749 2864 47d8 1b92 .........I(dG...
00000190: 39e7 9aec 8f0e 9e93 117a 6773 b710 ae53 9........zgs...S
000001a0: cd01 17ee b30e d9c1 15e6 6186 7a5c dc26 ..........a.z\.&
000001b0: 9750 1d51 610a d594 10ea f3be 4b7a 2c37 .P.Qa.......Kz,7
000001c0: 2f85 7a14 8fc4 a696 304d 4bdf c143 8db3 /.z.....0MK..C..
000001d0: d785 8a96 3085 2acc 274a a358 c635 8d37 ....0.*.'J.X.5.7
000001e0: 5f37 0f25 8ff5 6854 4a1f f6ad 1fc7 dbba _7.%..hTJ.......
000001f0: 51ed 517b 8da2 4b34 8d77 e5b2 ec46 7a18 Q.Q{..K4.w...Fz.
00000200: ffe8 3ade 6fed b2f2 99a3 bae3 c949 9ab5 ..:.o........I..
00000210: ab75 d897 d53c b258 a555 1b07 63d6 a679 .u...<.X.U..c..y
00000220: 4a51 5ead a23a 6a72 9eb6 d569 960b f3dc JQ^..:jr...i....
00000230: 9ceb 53fa 658f 345f ad07 6f6f efce 06ef ..S.e.4_..oo....
00000240: 0677 b791 cef2 f620 57bd 1b9c 4521 b241 .w..... W...E!.A
00000250: 4d83 2894 2eaf a140 8102 050a 1428 50a0 M.(....@.....(P.
00000260: 4081 0205 0a14 2850 a040 8102 050a 1428 @.....(P.@.....(
00000270: 50a0 4081 0205 0a14 2850 a040 8102 050a P.@.....(P.@....
00000280: 1428 50a0 4081 0205 0a14 2850 a040 8102 .(P.@.....(P.@..
00000290: 050a 1428 50a0 4081 0205 0a14 2850 a040 ...(P.@.....(P.@
000002a0: 8102 050a 1428 50a0 4081 0205 0a14 2850 .....(P.@.....(P
000002b0: a040 8102 050a 1428 50a0 4081 0205 0a14 .@.....(P.@.....
000002c0: 2850 a040 8102 050a 1428 50a0 4081 0205 (P.@.....(P.@...
000002d0: 0a14 2850 a040 8102 050a 1428 50a0 4081 ..(P.@.....(P.@.
000002e0: 0205 0a14 2850 a040 8102 050a 1428 50a0 ....(P.@.....(P.
000002f0: 4081 0205 0a14 2850 a040 8102 050a 1428 @.....(P.@.....(
00000300: 50a0 4081 0205 0a14 2850 a040 8102 050a P.@.....(P.@....
00000310: 1428 50a0 4081 0205 0a14 2850 a040 8102 .(P.@.....(P.@..
00000320: 050a 1428 50a0 4081 0205 0a14 2850 a040 ...(P.@.....(P.@
00000330: 8102 050a 1428 50a0 4081 0205 0a14 2850 .....(P.@.....(P
00000340: a040 8102 050a 1428 50a0 4081 0205 0a14 .@.....(P.@.....
00000350: 2850 a040 8102 050a 1428 50a0 4081 0205 (P.@.....(P.@...
00000360: 0a14 2850 a040 8102 050a 1428 50a0 4081 ..(P.@.....(P.@.
00000370: 0205 0a14 2850 a01c 14ca 7012 cbb4 a6e9 ....(P....p.....
00000380: e6db e6b1 e4b1 9e4c 4ae9 d3be f5f3 745b .......LJ.....t[
00000390: 37a9 3d6a af49 7489 a6e9 ae5c 96dd 488f 7.=j.It....\..H.
000003a0: d31f 5da7 fbad 5d56 3e73 5277 7cf5 aa7b ..]...]V>sRw|..{
000003b0: 3fbc df7c e986 c3ba 5ee4 3c6f 74f7 c3e1 ?..|....^.<ot...
000003c0: 301a bb45 d795 9afb fbdc 1495 65d5 6d9b 0..E........e.m.
000003d0: baf7 a5b4 a87d 4a5b d7fd b667 b788 ec27 .....}J[...g...'
000003e0: c5d8 28bc b96a 9eda 7a50 524d 290a a5cb ..(..j..zPRM)...
000003f0: cbef 38cb c3ad f690 0100 ..8.......
(Sì, è così ripetitivo che puoi persino vedere le ripetizioni dopo che è stato compresso).
La domanda dice "Consiglio vivamente anche di non eseguire il programma in TIO. Non solo TIO è più lento dell'interprete desktop, ma scadrà anche in circa un minuto. Sarebbe estremamente impressionante se qualcuno riuscisse a ottenere un punteggio abbastanza basso per essere eseguito il loro programma prima che TIO scadesse. " Posso farlo! Ci vogliono circa 20 secondi per funzionare su TIO, usando l'interprete Ruby: provalo online!
Il programma (leggibile)
Ora ho dato una versione del programma che i computer possono leggere, proviamo una versione che gli umani possono leggere. Ho convertito i byte che compongono il quine nella tabella codici 437 (se hanno il bit alto impostato) o nelle immagini di controllo Unicode (se sono codici di controllo ASCII), ho aggiunto spazi bianchi (qualsiasi spazio bianco preesistente è stato convertito in immagini di controllo ), codificato per la lunghezza di esecuzione utilizzando la sintassi «string×length»
e alcuni bit pesanti di dati sono stati eliminati:
␠
(((()()()()){}))
{{}
(({})[(()()()())])
(({})(
{{}{}((()[()]))}{}
(((((((({})){}){}{})){}{}){}){}())
{
({}(
(␀␀!S␠su! … many more comment characters … oq␝qoqoq)
(«()×35» («()×44» («()×44» («()×44» («()×44» («()×45»
… much more data encoded the same way …
(«()×117»(«()×115»(«()×117»
«000010101011┬â┬ … many more comment characters … ┬â0┬â┬à00␈␈
)[({})(
([({})]({}{}))
{
((()[()]))
}{}
{
{
({}(((({}())[()])))[{}()])
}{}
(({}))
((()[()]))
}{}
)]{}
%Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'×almost 241»
,444454545455┬ç┬ … many more comment characters … -a--┬ü␡┬ü-a␡┬ü
)[{}()])
}{}
{}({}())
)[{}])
(({})(()()()()){})
}{}{}␊
(Il "quasi 241" è perché alla 241a copia manca il finale '
, ma è identico agli altri 240.)
Spiegazione
A proposito dei commenti
La prima cosa da spiegare è: che succede con i personaggi non stampabili e altre cianfrusaglie che non sono comandi Mini-Flak? Potresti pensare che aggiungere commenti al quine renda le cose più difficili, ma questa è una competizione di velocità (non una competizione di dimensioni), il che significa che i commenti non danneggiano la velocità del programma. Nel frattempo, Brain-Flak, e quindi Mini-Flak, scarica il contenuto dello stack sull'output standard; se fosse necessario assicurarsi che lo stack contenesse soloi personaggi che compongono i comandi del tuo programma, dovresti spendere dei cicli per pulire lo stack. Così com'è, Brain-Flak ignora la maggior parte dei personaggi, quindi fintanto che ci assicuriamo che gli elementi della pila spazzatura non siano comandi Brain-Flak validi (rendendolo un poliglotta Brain-Flak / Mini-Flak) e non siano negativi o esterni nell'intervallo Unicode, possiamo semplicemente lasciarli in pila, consentire loro di essere emessi e mettere lo stesso carattere nel nostro programma nello stesso posto per conservare la proprietà quine.
C'è un modo particolarmente importante in cui possiamo trarne vantaggio. Il quine funziona usando una lunga stringa di dati e praticamente tutto l'output del quine viene prodotto formattando la stringa di dati in vari modi. C'è solo una stringa di dati, nonostante il programma abbia più pezzi; quindi dobbiamo essere in grado di utilizzare la stessa stringa di dati per stampare parti diverse del programma. Il trucco "dati inutili non importa" ci permette di farlo in un modo molto semplice; memorizziamo i caratteri che compongono il programma nella stringa di dati aggiungendo o sottraendo un valore al o dal loro codice ASCII. In particolare, i caratteri che compongono l'inizio del programma vengono memorizzati come loro codice ASCII + 4, i caratteri che compongono la sezione che viene ripetuta quasi 241 volte come il loro codice ASCII - 4,ogni carattere della stringa di dati con un offset; se, ad esempio, lo stampiamo con 4 aggiunti ad ogni codice carattere, otteniamo una ripetizione della sezione ripetuta, con alcuni commenti prima e dopo. (Questi commenti sono semplicemente le altre sezioni del programma, con i codici carattere spostati in modo che non formino alcun comando Brain-Flak valido, perché è stato aggiunto un offset errato. Dobbiamo schivare i comandi Brain-Flak, non solo Mini- Flak comanda, per evitare di violare la parte a fonte limitata della domanda; la scelta degli offset è stata progettata per garantire ciò.)
A causa di questo trucco per i commenti, in realtà dobbiamo solo essere in grado di produrre la stringa di dati formattata in due modi diversi: a) codificata nello stesso modo della sorgente, b) come codici di carattere con un offset specificato aggiunto ad ogni codice. Questa è un'enorme semplificazione che rende totalmente utile la lunghezza aggiunta.
Struttura del programma
Questo programma è composto da quattro parti: l'introduzione, la stringa di dati, il formattatore della stringa di dati e l'outro. L'introduzione e l'outro sono fondamentalmente responsabili dell'esecuzione della stringa di dati e del suo formattatore in un ciclo, specificando ogni volta il formato appropriato (ovvero se codificare o compensare e quale offset utilizzare). La stringa di dati è solo un dato, ed è l'unica parte della quina per la quale i caratteri che lo compongono non sono specificati letteralmente nella stringa di dati (farlo sarebbe ovviamente impossibile, in quanto dovrebbe essere più lungo di se stesso); è quindi scritto in un modo particolarmente facile da rigenerare da se stesso. Il formattatore della stringa di dati è composto da 241 parti quasi identiche, ognuna delle quali formatta un dato specifico dal 241 nella stringa di dati.
Ogni parte del programma può essere prodotta tramite la stringa di dati e il suo formattatore come segue:
- Per produrre l'outro, formattare la stringa di dati con offset +8
- Per produrre il formattatore della stringa di dati, formattare la stringa di dati con offset +4, 241 volte
- Per produrre la stringa di dati, formatta la stringa di dati codificandola nel formato di origine
- Per produrre l'introduzione, formattare la stringa di dati con offset -4
Quindi tutto ciò che dobbiamo fare è esaminare come funzionano queste parti del programma.
La stringa di dati
(«()×35» («()×44» («()×44» («()×44» («()×44» («()×45» …
Abbiamo bisogno di una semplice codifica per la stringa di dati in quanto dobbiamo essere in grado di invertire la codifica nel codice Mini-Flak. Non puoi essere molto più semplice di così!
L'idea chiave alla base di questo quine (a parte il trucco dei commenti) è notare che in pratica esiste solo un posto in cui è possibile memorizzare grandi quantità di dati: le "somme dei valori di ritorno del comando" all'interno dei vari livelli di annidamento della sorgente del programma. (Questo è comunemente noto come terzo stack, anche se Mini-Flak non ha un secondo stack, quindi "stack di lavoro" è probabilmente un nome migliore nel contesto di Mini-Flak.) Le altre possibilità per l'archiviazione dei dati sarebbero lo stack principale / primo (che non funziona perché è lì che deve arrivare il nostro output e non possiamo spostare l'output oltre l'archiviazione in un modo remotamente efficiente) e codificato in un bignum in un singolo elemento dello stack (che non è adatto a questo problema perché ci vuole tempo esponenziale per estrarre dati da esso); quando li elimini, lo stack di lavoro è l'unica posizione rimanente.
Per "archiviare" i dati su questo stack, utilizziamo comandi non bilanciati (in questo caso, la prima metà di un (…)
comando), che verranno successivamente bilanciati nel formattatore della stringa di dati. Ogni volta che chiudiamo uno di questi comandi nel formatter, esso spingerà la somma di un dato preso dalla stringa di dati e i valori di ritorno di tutti i comandi a quel livello di annidamento nel formatter; possiamo assicurarci che quest'ultimo si aggiunga a zero, quindi il formatter vede semplicemente i singoli valori presi dalla stringa di dati.
Il formato è molto semplice:, (
seguito da n copie di ()
, dove n è il numero che vogliamo memorizzare. (Si noti che ciò significa che è possibile memorizzare solo numeri non negativi e l'ultimo elemento della stringa di dati deve essere positivo.)
Un punto leggermente non intuitivo sulla stringa di dati è l'ordine in cui si trova. Il "start" della stringa di dati è la fine più vicina all'inizio del programma, ovvero il livello di annidamento più esterno; questa parte viene formattata per ultima (poiché il formatter va dai livelli di annidamento più interni a quelli più esterni). Tuttavia, nonostante sia stato formattato per ultimo, viene stampato per primo, poiché i valori inseriti per primi nello stack vengono stampati per ultimi dall'interprete Mini-Flak. Lo stesso principio si applica al programma nel suo insieme; dobbiamo prima formattare l'outro, quindi il formattatore della stringa di dati, quindi la stringa di dati, quindi l'intro, ovvero il contrario dell'ordine in cui sono memorizzati nel programma.
Il formattatore della stringa di dati
)[({})(
([({})]({}{}))
{
((()[()]))
}{}
{
{
({}(((({}())[()])))[{}()])
}{}
(({}))
((()[()]))
}{}
)]{}
Il formattatore della stringa di dati è composto da 241 sezioni ognuna con un codice identico (una sezione ha un commento leggermente diverso), ognuna delle quali formatta un carattere specifico della stringa di dati. (Non potremmo usare un ciclo qui: abbiamo bisogno di uno sbilanciato )
per leggere la stringa di dati tramite l'abbinamento del suo sbilanciato (
, e non possiamo mettere uno di quelli all'interno di un {…}
ciclo, l'unica forma di ciclo che esiste. Quindi invece, " srotolare "il formattatore e ottenere semplicemente l'intro / outro per generare la stringa di dati con l'offset del formatter 241 volte.)
)[({})( … )]{}
La parte più esterna di un elemento formatter legge un elemento della stringa di dati; la semplicità della codifica della stringa di dati porta a una piccola complessità nella lettura. Iniziamo chiudendo l'ineguagliato (…)
nella stringa di dati, quindi negate ( […]
) due valori: il dato che abbiamo appena letto dalla stringa di dati ( ({})
) e il valore restituito del resto del programma. Copiamo il valore restituito del resto dell'elemento formatter con (…)
e aggiungiamo la copia alla versione negata con {}
. Il risultato finale è che il valore di ritorno dell'elemento stringa di dati e dell'elemento formatter insieme è l'origine meno l'origine meno il valore di ritorno più il valore di ritorno, o 0; ciò è necessario per fare in modo che l'elemento stringa di dati successivo produca il valore corretto.
([({})]({}{}))
Il formattatore utilizza l'elemento stack superiore per sapere in quale modalità è (0 = formato nella formattazione della stringa di dati, qualsiasi altro valore = offset con cui generare). Tuttavia, dopo aver letto la stringa di dati, l'origine è in cima al formato nello stack e li vogliamo viceversa. Questo codice è una variante più breve del codice di scambio Brain-Flak, prendendo da sopra b a b sopra a + b ; non solo è più breve, ma è anche (in questo caso specifico) più utile, perché l'effetto collaterale dell'aggiunta di b a a non è problematico quando b è 0 e quando b non è 0, esegue il calcolo dell'offset per noi.
{
((()[()]))
}{}
{
…
((()[()]))
}{}
Brain-Flak ha solo una struttura di flusso di controllo, quindi se vogliamo qualcosa di diverso da un while
loop, ci vorrà un po 'di lavoro. Questa è una struttura "negata"; se c'è uno 0 in cima allo stack, lo rimuove, altrimenti posiziona uno 0 in cima allo stack. (Funziona abbastanza semplicemente: fintanto che non c'è uno 0 in cima allo stack, spingi 1 - 1 nello stack due volte; quando hai finito, fai apparire l'elemento dello stack in alto.)
È possibile inserire il codice all'interno di una struttura negata, come mostrato qui. Il codice verrà eseguito solo se la parte superiore dello stack era diversa da zero; quindi se abbiamo due strutture negate, supponendo che i primi due elementi dello stack non siano entrambi zero, si annulleranno a vicenda, ma qualsiasi codice all'interno della prima struttura verrà eseguito solo se l'elemento dello stack superiore era diverso da zero e il codice all'interno la seconda struttura verrà eseguita solo se l'elemento dello stack superiore era zero. In altre parole, questo è l'equivalente di un'istruzione if-then-else.
Nella clausola "then", che viene eseguita se il formato è diverso da zero, in realtà non abbiamo nulla da fare; ciò che vogliamo è spingere i dati + offset nello stack principale (in modo che possa essere emesso alla fine del programma), ma è già lì. Quindi dobbiamo solo affrontare il caso della codifica dell'elemento stringa di dati in formato sorgente.
{
({}(((({}())[()])))[{}()])
}{}
(({}))
Ecco come lo facciamo. La {({}( … )[{}()])}{}
struttura dovrebbe essere familiare come un ciclo con un numero specifico di iterazioni (che funziona spostando il contatore del ciclo sullo stack di lavoro e tenendolo lì; sarà al sicuro da qualsiasi altro codice, perché l'accesso allo stack di lavoro è legato a il livello di annidamento del programma). Il corpo del loop è ((({}())[()]))
, che crea tre copie dell'elemento stack superiore e aggiunge 1 al valore più basso. In altre parole, trasforma un 40 in cima allo stack in 40 sopra 40 sopra 41, o visto come ASCII, (
in (()
; esegue ripetutamente farà (
in (()
dentro (()()
in (()()()
e così via, e quindi è un modo semplice per generare nostra stringa dati (supponendo che c'è una (
in cima alla pila già).
Una volta terminato il ciclo, (({}))
duplica la parte superiore dello stack (in modo che ora inizi ((()…
piuttosto che (()…
. Il lead (
verrà utilizzato dalla copia successiva del formattatore della stringa di dati per formattare il carattere successivo (lo espanderà in (()(()…
quindi (()()(()…
e così via, quindi questo genera la separazione (
nella stringa di dati).
%Wwy$%Y%ywywy$wy$%%%WwyY%$$wy%$$%$%$%$%%wy%ywywy'
C'è un ultimo bit di interesse nel formattatore di stringhe di dati. OK, quindi per lo più questo è solo l'outro spostato verso il basso di 4 punti di codice; tuttavia, quell'apostrofo alla fine potrebbe sembrare fuori posto. '
(codepoint 39) si sposterebbe in +
(codepoint 43), che non è un comando Brain-Flak, quindi potresti aver indovinato che è lì per qualche altro scopo.
Il motivo è che il formattatore di stringhe di dati si aspetta che ci sia già uno (
stack (non contiene un 40 letterale da nessuna parte). Il'
è in realtà all'inizio del blocco che si ripete per creare il formattatore della stringa di dati, non la fine, quindi dopo che i caratteri del formattatore della stringa di dati sono stati inseriti nello stack (e il codice sta per passare alla stampa della stringa di dati stesso), l'outro regola i 39 in cima alla pila in un 40, pronto per il formatter (il formatter in esecuzione stesso questa volta, non la sua rappresentazione nella fonte) per usarlo. Ecco perché abbiamo "quasi 241" copie del formatter; nella prima copia manca il suo primo carattere. E quel personaggio, l'apostrofo, è uno dei soli tre caratteri nella stringa di dati che non corrispondono al codice Mini-Flak da qualche parte nel programma; è puramente un metodo per fornire una costante.
L'intro e l'outro
(((()()()()){}))
{{}
(({})[(()()()())])
(({})(
{{}{}((()[()]))}{}
(((((((({})){}){}{})){}{}){}){}())
{
({}(
(␀␀!S␠su! … many more comment characters … oq␝qoqoq)
…
)[{}()])
}{}
{}({}())
)[{}])
(({})(()()()()){})
}{}{}␊
L'introduzione e l'outro sono concettualmente la stessa parte del programma; l'unica ragione per cui facciamo una distinzione è che l'outro deve essere emesso prima della stringa di dati e del suo formattatore (in modo che stampi dopo di loro), mentre l'intro deve essere prodotto dopo di loro (stampando prima di loro).
(((()()()()){}))
Iniziamo posizionando due copie di 8 in pila. Questo è l'offset per la prima iterazione. La seconda copia è perché il ciclo principale prevede che ci sia un elemento spazzatura in cima allo stack sopra l'offset, lasciato indietro dal test che decide se esiste il ciclo principale, e quindi dobbiamo inserire un elemento spazzatura in modo che non getta via l'elemento che realmente vogliamo; una copia è il modo più rapido (quindi il più veloce da produrre) per farlo.
Esistono altre rappresentazioni del numero 8 che non sono più lunghe di questa. Tuttavia, quando si va per il codice più veloce, questa è sicuramente l'opzione migliore. Per prima cosa, l'uso ()()()()
è più veloce di, diciamo, (()()){}
perché nonostante entrambi siano lunghi 8 caratteri, il primo è un ciclo più veloce, perché (…)
viene conteggiato come 2 cicli, ma ()
solo come uno. Il salvataggio di un ciclo è trascurabile rispetto a una considerazione molto più grande per un quine , tuttavia: (
e )
ha punti di codice molto più bassi di {
e }
, quindi generare il frammento di dati per loro sarà molto più veloce (e il frammento di dati occuperà meno spazio nel codice, pure).
{{} … }{}{}
Il ciclo principale. Questo non conta le iterazioni (è un while
ciclo, non un for
ciclo e utilizza un test per scoppiare). Una volta uscito, scartiamo i primi due elementi dello stack; l'elemento in alto è uno 0 innocuo, ma l'elemento in basso sarà il "formato da utilizzare nella prossima iterazione", che (essendo un offset negativo) è un numero negativo e se ci sono numeri negativi nello stack quando il Mini -Flak esce dal programma, l'interprete si arresta in modo anomalo nel tentativo di emetterli.
Poiché questo ciclo utilizza un test esplicito per essere superato, il risultato di quel test verrà lasciato nello stack, quindi lo scartiamo come prima cosa che facciamo (il suo valore non è utile).
(({})[(()()()())])
Questo codice spinge 4 e f - 4 sopra un elemento stack f , lasciando tale elemento in posizione. Stiamo calcolando il formato per la prossima iterazione in anticipo (mentre abbiamo le 4 costanti a portata di mano) e contemporaneamente mettiamo lo stack nell'ordine corretto per le prossime parti del programma: useremo f come formato per questa iterazione e prima è necessario il 4.
(({})( … )[{}])
Questo salva una copia di f - 4 nello stack di lavoro, in modo che possiamo usarlo per la prossima iterazione. (Il valore di f sarà ancora presente a quel punto, ma sarà in un posto scomodo sullo stack, e anche se potessimo spostarlo nel posto corretto, dovremmo passare dei cicli sottraendo 4 da esso, e stampa ciclicamente il codice per fare quella sottrazione. Molto più semplice semplicemente memorizzarlo ora.)
{{}{}((()[()]))}{}
Un test per vedere se l'offset è 4 (cioè f - 4 è 0). In tal caso, stiamo stampando il formattatore della stringa di dati, quindi dobbiamo eseguire la stringa di dati e il suo formattatore 241 volte anziché una sola volta su questo offset. Il codice è abbastanza semplice: se f - 4 è diverso da zero, sostituire f - 4 e il 4 stesso con una coppia di zeri; quindi in entrambi i casi, pop l'elemento stack superiore. Ora abbiamo un numero sopra f in pila, o 4 (se vogliamo stampare questa iterazione 241 volte) o 0 (se vogliamo stamparlo solo una volta).
(
((((((({})){}){}{})){}{}){}){}
()
)
Questa è una specie interessante di costante Brain-Flak / Mini-Flak; la lunga fila qui rappresenta il numero 60. Potresti essere confuso dalla mancanza di ()
, che normalmente si trovano ovunque nelle costanti Brain-Flak; questo non è un numero normale, ma un numero della Chiesa, che interpreta i numeri come un'operazione di duplicazione. Ad esempio, il numero della Chiesa per 60, visto qui, crea 60 copie del suo input e le combina tutte insieme in un unico valore; in Brain-Flak, le uniche cose che possiamo combinare sono i numeri regolari, in aggiunta, quindi finiamo per aggiungere 60 copie della parte superiore della pila e moltiplicando così la parte superiore della pila per 60.
Come nota a margine, puoi usare un cercatore di numeri Underload , che genera numeri Church nella sintassi Underload, per trovare il numero appropriato anche in Mini-Flak. I numeri di sottocarico (diversi da zero) utilizzano le operazioni "elemento stack superiore duplicato" :
e "combina due elementi stack superiori" *
; sia quelle operazioni esistono in Brain-Flak, quindi basta tradurre :
a )
, *
a {}
, anteporre una {}
, e aggiungere abbastanza (
all'inizio per equilibrio (questo sta usando uno strano mix della pila principale e la pila di lavoro, ma funziona).
Questo particolare frammento di codice utilizza il numero di chiesa 60 (in effetti uno snippet "moltiplica per 60"), insieme a un incremento, per generare l'espressione 60 x + 1. Quindi, se avessimo un 4 dal passaggio precedente, questo ci dà un valore di 241, o se avessimo uno 0, otteniamo solo un valore di 1, ovvero questo calcola correttamente il numero di iterazioni di cui abbiamo bisogno.
La scelta di 241 non è casuale; era un valore scelto per essere a) approssimativamente la lunghezza alla quale il programma sarebbe comunque finito eb) 1 più di 4 volte un numero circolare. I numeri rotondi, 60 in questo caso, tendono ad avere rappresentazioni più brevi come numeri della Chiesa perché hai più flessibilità nei fattori da copiare. Il programma contiene in seguito un'imbottitura per portare esattamente la lunghezza a 241.
{
({}(
…
)[{}()])
}{}
Questo è un ciclo for, come quello visto in precedenza, che esegue semplicemente il codice al suo interno un numero di volte uguale alla parte superiore dello stack principale (che consuma; il contatore del loop stesso è memorizzato sullo stack di lavoro, ma la visibilità di che è legato al livello di annidamento del programma e quindi è impossibile per tutto tranne che per il ciclo stesso interagire con esso). Questo in realtà esegue la stringa di dati e il suo formattatore 1 o 241 volte, e poiché ora abbiamo estratto tutti i valori che stavamo usando per il nostro calcolo del flusso di controllo dallo stack principale, abbiamo il formato da usare sopra di esso, pronto per il formatter da usare.
(␀␀!S␠su! … many more comment characters … oq␝qoqoq)
Il commento qui non è del tutto privo di interesse. Per prima cosa, ci sono un paio di comandi Brain-Flak; il )
fine è naturalmente generato come effetto collaterale del modo le transizioni tra i vari segmenti del programma di lavoro, in modo che (
l'all'inizio è stato aggiunto manualmente per bilanciare (e nonostante la lunghezza del commento all'interno, mettendo un commento all'interno un ()
comando è ancora un ()
comando, quindi tutto ciò che fa è aggiungere 1 al valore restituito della stringa di dati e del suo formattatore, qualcosa che il ciclo for ignora del tutto).
In particolare, quei caratteri NUL all'inizio del commento chiaramente non sono compensati da nulla (anche la differenza tra +8 e -4 non è sufficiente per trasformare a (
in NUL). Si tratta di una semplice imbottitura per portare la stringa di dati a 239 elementi fino a 241 elementi (che si pagano facilmente da soli: occorrerebbero molto più di due byte per generare 1 vs. 239 anziché 1 vs. 241 quando si calcola il numero di iterazioni richieste ). NUL è stato usato come carattere di riempimento perché ha il punto di codice più basso possibile (rendendo il codice sorgente per la stringa di dati più breve e quindi più veloce per l'output).
{}({}())
Rilascia l'elemento stack superiore (il formato che stiamo utilizzando), aggiungi 1 al successivo (l'ultimo carattere da generare, ovvero il primo carattere da stampare, della sezione del programma che abbiamo appena formattato). Non abbiamo più bisogno del vecchio formato (il nuovo formato si nasconde nello stack di lavoro); e l'incremento è innocuo nella maggior parte dei casi, e modifica il carattere '
di formattazione della stringa di dati a un'estremità della sorgente in un (
(che è richiesto nello stack per la prossima volta che eseguiamo il formattatore, per formattare la stringa di dati). Abbiamo bisogno di una trasformazione come quella nell'outro o nell'intro, perché forzare ogni elemento di formattatore di stringhe di dati con cui iniziare (
lo renderebbe un po 'più complesso (poiché dovremmo chiudere il (
e quindi annullarne l'effetto in seguito), esaremmo in qualche modo necessario generare un extra (
da qualche parte perché abbiamo solo quasi 241 copie del formattatore, non tutti i 241 (quindi è meglio che un personaggio innocuo come '
è quello che manca).
(({})(()()()()){})
Infine, il test di uscita del loop. L'attuale top dello stack principale è il formato necessario per la prossima iterazione (che è appena tornata dallo stack di lavoro). Questo lo copia e aggiunge 8 alla copia; il valore risultante verrà scartato la prossima volta attorno al loop. Tuttavia, se abbiamo appena stampato l'introduzione, l'offset era -4, quindi l'offset per la "prossima iterazione" sarà -8; -8 + 8 è 0, quindi il ciclo uscirà anziché continuare sull'iterazione in seguito.