Suggerimenti per giocare a golf in brainfuck


23

Quali consigli generali hai per giocare a golf in brainfuck? Sto cercando idee che possano essere applicate ai problemi del codice golf in generale che siano almeno in qualche modo specifiche per Brainfuck (ad esempio "rimuovere i commenti" non è una risposta). Si prega di inviare un suggerimento per risposta.

Risposte:


25

Mettere un suggerimento per risposta sarebbe troppe risposte.

  • Impara a pensare in Brainfuck. È molto diverso da qualsiasi altra cosa. Leggi e scrivi, riscrivi e riscrivi molti programmi brainfuck. La lingua non ti dà molto su cui lavorare, quindi è importante usare ciò che ti dà in modo flessibile ed efficiente. Non lasciare che le astrazioni si frappongano tra te e la lingua, entra e affronta.

  • Mettiti comodo con il controllo del flusso non distruttivo. Per uscire da un ciclo decisionale, anziché azzerare la cella iniziale copiandola altrove e quindi copiandola nuovamente dopo aver lasciato il ciclo, spesso è meglio spostare il puntatore su uno zero preesistente nelle vicinanze. Sì, questo significa che il puntatore si troverà in punti diversi a seconda che tu abbia attraversato il ciclo, ma ciò significa anche che quei posti probabilmente hanno disposizioni diverse di zeri e nonzer vicini, che puoi usare per risincronizzare la posizione del puntatore usando un altro ciclo. Questa tecnica è fondamentale per una buona programmazione di Brainfuck e varie forme si dimostreranno costantemente utili.

  • Questo e il fatto che ogni >o i <costi significano che i dettagli del layout di memoria sono importanti. Prova tutte le varianti del layout di cui hai pazienza. E ricorda, il tuo layout di memoria non deve essere una mappatura rigida dei dati alle posizioni. Può trasformarsi nel corso dell'esecuzione.

  • Su una scala più ampia, considerare e persino provare a implementare una varietà di algoritmi diversi. Inizialmente non sarà ovvio esattamente quale algoritmo sarà il migliore; potrebbe anche non essere ovvio quale approccio di base sarà il migliore, e probabilmente sarà qualcosa di diverso da quello che sarebbe meglio in un linguaggio normale.

  • Se hai a che fare con dati di grandi dimensioni o variabili, vedi se esiste un modo per gestirli localmente, senza dover tenere traccia di quanto sia grande o della tua posizione numerica al suo interno.

  • Gli stessi dati possono essere due cose diverse. (Molto spesso, un numero o un carattere e anche un marcatore di posizione diverso da zero. Ma vedi random.b , dove un contatore di bit raddoppia come il valore di una cella di un automa cellulare.)

  • Lo stesso codice può fare due cose diverse, ed è molto più facile farlo in una lingua in cui il codice è generico come <+<. Stai attento a tali possibilità. In effetti, di tanto in tanto potresti notare, anche in quello che sembra essere un programma ben scritto, che ci sono piccole parti che potrebbero essere eliminate del tutto, nulla di aggiunto, e la cosa, per caso, continuerebbe a funzionare senza problemi.

  • Nella maggior parte delle lingue, si utilizza frequentemente un compilatore o un interprete per verificare il comportamento del programma. Il linguaggio Brainfuck richiede un maggiore controllo concettuale; se hai bisogno di un compilatore per dirti cosa fa il tuo programma, non hai una conoscenza abbastanza ferma del tuo programma e probabilmente dovrai fissarlo ancora un po '- almeno se vuoi avere un'immagine abbastanza chiara di l'alone concettuale di programmi simili per essere bravi a golf. Con la pratica, produrrai una dozzina di versioni del tuo programma prima di provare a eseguirne uno, e a quel punto sarai sicuro al 95% che il tuo più breve funzionerà correttamente.

  • In bocca al lupo! Pochissime persone si preoccupano di scrivere Brainfuck in modo conciso, ma penso che sia l'unico modo in cui la lingua può giustificare la continua attenzione, come una forma d'arte straordinariamente oscura.


3
Leggere questo mi fa venire voglia di provare a programmare in Brainfuck ora ..
Claudiu,

Merda, pensavo di aver riconosciuto il tuo nome. Grande fan dei tuoi programmi brainfuck, in particolare il tuo interprete super corto!
Jo King,

"di tanto in tanto potresti notare ... che ci sono piccole porzioni che potrebbero essere eliminate del tutto, nulla di aggiunto e che la cosa, per caso, continuerebbe a funzionare senza problemi." Questo è così vero, soprattutto per un principiante. Ho sempre scritto una sola risposta in BF per quanto posso ricordare, ma la sua cronologia delle revisioni contiene almeno 3 casi in cui stavo giocando con il programma e casualmente è andato ", ehi, il programma funziona ancora se rimuovo questo bit! "
ETHproductions

"Prova tutte le varianti del tuo layout di cui hai pazienza", o puoi avere la forza bruta : P
Esolanging Fruit,

9

Alcuni consigli qui:

costanti:

La pagina delle costanti degli Esolang contiene un elenco estremamente utile dei modi più brevi per creare valori specifici. Mi ritrovo a consultare questa pagina almeno due volte per programma.

L'inizio di tutto:

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

Questo imposta il nastro nel formato 3 * n ^ 2, che sembra

3 6 12 24 48 96 192 128 0 0 '

Perché è così importante?

Scendiamo l'elenco:

  • 3 e 6 sono noiosi
  • 12: vicino a 10 (newline) o 13 (ritorno a capo). Può essere utilizzato anche per il contatore per 0-9
  • 24: vicino a 26, il numero di lettere dell'alfabeto
  • 48: ASCII per 0
  • 96: vicino a 97, ASCII per a
  • 196 e 128: 196-128 = 64, vicino a 65, ASCII per A.

Da questo unico algoritmo, siamo all'inizio praticamente di ogni sequenza nell'intervallo ASCII, insieme a un contatore per ciascuno e una nuova riga a portata di mano.

Un esempio pratico:

Stampa di tutte le lettere maiuscole e minuscole e cifre.

Con algoritmo:

+++[[<+>>++<-]>]<<[-<->]<<<<++[->>+.>+.<<<]<--[>>.+<<-]

Senza:

+++++++++++++[->+++++++>++>+++++>++++>+<<<<<]>+++++>[-<+.>>.+<]>>---->---[-<.+>]

Spendiamo la maggior parte dei byte solo inizializzando il nastro nel secondo esempio. Alcuni di questi sono compensati dai movimenti extra nel primo esempio, ma questo metodo ha chiaramente il vantaggio.

Un paio di altri algoritmi interessanti nella stessa vena:

3 * 2 ^ n + 1:

+++[[<+>>++<-]+>]
Tape: 4 7 13 25 49 65 197 129 1 0'

Ciò compensa i valori di 1, il che compie alcune cose. Rende il 12 un ritorno a capo, il 64 l'inizio effettivo dell'alfabeto maiuscolo e il 24 più vicino a 26.

2 ^ n:

+[[<+>>++<-]>]
Tape: 1 2 4 8 16 32 64 128

Poiché 64 è valido per lettere maiuscole, 32 è ASCII per spazio e 128 può essere utilizzato come contatore per 26 (130/5 = 26). Ciò può salvare byte in determinate situazioni in cui non sono necessarie cifre e lettere minuscole.

Scegli l'implementazione adatta alla domanda:

  • Le celle negative sono quasi sempre utili e non c'è motivo di evitarle (a meno che non cambi il tuo conto)
  • Quasi la stessa cosa con le celle di avvolgimento, ancora di più perché molte costanti usano il wrapping.
  • Le dimensioni arbitrarie delle celle sono utili per infinite sequenze matematiche, come il calcolo infinito della sequenza di Fibonacci ( +[[-<+>>+>+<<]>]) o l'elaborazione di numeri più grandi / negativi. Il rovescio della medaglia è che alcuni metodi comuni, come [-]e [->+<]non possono essere fatti valere, nel caso in cui il numero sia negativo.
  • EOF come 0, -1 o nessuna modifica. Di solito è preferibile 0, in quanto è possibile eseguire il ciclo su un intero input senza ulteriori controlli. -1 è utile quando si esegue il ciclo su strutture di array. Non ho ancora trovato un uso per nessun cambiamento :(.

Tieni traccia di ciò che sta succedendo:

In ogni momento dovresti avere commenti su dove dovrebbe essere il puntatore in relazione ai dati che lo circondano e assicurarti di conoscere l'intervallo dei possibili valori di ogni cella. Questo è particolarmente importante quando hai diviso il puntatore prima di un ciclo, perché dopo vorrai unire nuovamente le due possibilità.

In ogni momento, il mio codice è pieno di commenti su ogni altra riga che assomigliano a questo:

*0 *dat a_1 ?  0' !0 0*
 or
*0 *dat 0' ap1 0  !0 0*

Un altro consiglio è quello di assegnare significati speciali ai simboli. Nell'esempio sopra, 'è dove si trova il puntatore, *significa ripetizione in quella direzione, ?significa una cella con valore sconosciuto, !0significa una cella diversa da zero, _è un sostituto -ed pè un sostituto +. orimplica che il nastro potrebbe apparire come una delle rappresentazioni e deve essere gestito come tale.

Il tuo schema di simboli non deve necessariamente essere uguale al mio (che ha alcuni difetti), deve solo essere coerente. Questo è anche estremamente utile durante il debug, perché è possibile eseguirlo fino a quel punto e confrontare il nastro effettivo con quello che si dovrebbe avere, il che può evidenziare potenziali difetti nel codice.


5

Il mio consiglio principale sarebbe no.

OK, va bene, vuoi qualcosa di più utile di così. BF è già un linguaggio molto conciso, ma ciò che ti uccide davvero è l'aritmetica, che in realtà deve essere fatta all'unanimità. Vale la pena leggere la pagina delle costanti di Esolang per scoprire esattamente come scrivere in modo efficiente numeri grandi e sfruttare il wrapping laddove possibile.

Anche l'accesso alla memoria è molto costoso. Dato che stai leggendo da un nastro, devi tenere a mente dove si sta muovendo la testa in qualsiasi momento. A differenza di altre lingue in cui si può solo scrivere a, b, c, in bf si deve spostare in modo esplicito il capo un numero di byte a sinistra oa destra, quindi è necessario essere consapevoli di dove memorizzare ciò. Sono abbastanza sicuro che organizzare la tua memoria nel modo ottimale sia NP-difficile, quindi buona fortuna con quello.


5

In questa risposta farò riferimento a una cella specifica sul nastro molte volte. Non importa quale cella sia, ma è la stessa cella nell'intera risposta. Ai fini di questo post, chiamerò quella cella "Todd".

Quando si tenta di impostare una cella su un valore costante, a volte paga per non finirla immediatamente. Ad esempio, supponiamo che tu voglia che Todd contenga 30. Più avanti nel tuo codice (che può modificare il valore di Todd ma non lo legge mai) torni a Todd. Se il valore di Todd è 0, il programma termina. Altrimenti, il valore di Todd viene stampato per sempre.

Secondo la pagina esolangs.org delle costanti di Brainfuck (che probabilmente potrebbe essere l'argomento di un suggerimento da solo!) Il modo più breve per ottenere 30 è >+[--[<]>>+<-]>+. Quel lead >è lì solo per garantire che nulla alla sinistra del puntatore sia modificato, ma in questo caso assumeremo che non ci interessi a questo e lo rilasciamo. Usando quel codice, il tuo codice sarebbe simile a questo:

+[--[<]>>+<-]>+(MISC. CODE)(GO TO TODD)[.]

Puoi pensare al primo blocco di codice come questo:

(SET TODD TO 30)(MISC. CODE)(GO TO TODD)[.]

Ma ricordate gli ultimi due caratteri in quel pezzo: >+. È altrettanto valido pensarlo in questo modo:

(SET TODD TO 29)(GO TO TODD)(ADD 1 TO TODD)(MISC. CODE)(GO TO TODD)[.]

Si noti che (GO TO TODD)due volte! Puoi invece scrivere il tuo codice in questo modo:

(SET TODD TO 29)(MISC. CODE)(GO TO TODD)(ADD 1 TO TODD)[.]
+[--[<]>>+<-](MISC. CODE)(GO TO TODD)+[.]

Supponendo che il numero di byte necessario (GO TO TODD)sia lo stesso prima, una mossa in meno == un byte in meno! A volte il fatto che la tua posizione di partenza sia cambiata toglie quel vantaggio, ma non sempre.


0

Un piccolo suggerimento per sfide senza input. È possibile utilizzare ,invece di [-], se è necessario cancellare rapidamente la cella, poiché la maggior parte degli interpreti (incluso quello TIO.run) imposterà il contenuto della cella sulla rappresentazione EOF su zero. Questo rende i programmi un po 'insostenibili, ma chi se ne frega comunque nel code golf?

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.