Suggerimenti per giocare a golf in Brain-Flak


24

Brain-flak è un linguaggio turing-tarpit basato su stack, scritto in collaborazione tra me, DJMcMayhem e 1000000000 .

Alcuni utenti hanno molta esperienza nei modi misteriosi di Brain-Flak. Quindi ho pensato che fosse una buona idea impostare questa domanda come un modo per noi, e si spera anche per gli altri, di condividere le nostre conoscenze con la comunità e abbassare il livello di accesso a questo linguaggio "progettato per essere doloroso da usare". E forse anche insegnare a quelli di noi con più esperienza alcuni nuovi trucchi.

Quindi, quali consigli hai per giocare a golf in brain-flak? Sto cercando idee che possano essere applicate ai problemi del codice golf in generale che siano almeno in qualche modo specifiche per il cervello-flak (ad esempio "rimuovere i commenti" non è una risposta).

Si prega di inviare un suggerimento per risposta.

Risposte:


22

Usa il terzo stack

Se hai letto il titolo potresti essere un po 'confuso. Sicuramente ci sono solo due pile in Brain-Flak? Comunque ti assicuro che esiste ed è uno degli strumenti più potenti se non il più potente nella scrittura e nel golf di Brain-Flak.


Che cos'è il "terzo stack"?

Ogni programma Brain-Flak utilizza il terzo stack in un modo o nell'altro, ma la maggior parte dell'utilizzo continua dietro le quinte ed è spesso utile semplicemente ignorare il fatto che esiste. Ogni parentesi nel programma aggiunge o rimuove un oggetto dalla pila. Tre delle parentesi graffe aperte ([<aggiungono tutti un oggetto alla pila, mentre i loro tre coniugati )]>rimuovono tutti un oggetto dalla pila. Il valore dell'elemento nello stack è il valore dell'ambito corrente del programma e l'utilizzo di nilad modifica questo valore in alcuni modi. La parentesi stretta )ha la funzione unica di spostare un elemento dalla terza pila alla pila corrente; una spinta.

Spero che questo ti stia diventando chiaro. Il terzo stack è una sorta di stack che ricorda i valori di ritorno del codice che è già stato eseguito. Esaminiamo un esempio di un semplice programma che tiene traccia delle due pile normali e della terza pila.

Esempio

Cammineremo attraverso il seguente programma. Questo programma viene inserito -3, 1, -2nello stack.

(([()()()])(()))

Iniziamo con tre parentesi graffe aperte, che spingono tutte a zero al terzo stack.

Le nostre pile ora sembrano così, la terza pila è quella a destra e la pila attiva ha un ^sotto:

        0
        0
  0  0  0
  ^

(([()()()])(()))
   ^

Ora abbiamo tre ()nilad. Questi non fanno nulla alle normali due pile, tuttavia ognuna ne aggiunge una all'inizio del terzo stack facendo apparire le nostre pile come:

        3
        0
  0  0  0
  ^

(([()()()])(()))
         ^

Ora incontriamo un ]come indicato prima che le parentesi graffe chiuse rimuovano un oggetto dalla terza pila, ma ]ha la funzione di sottrarre l'elemento che rimuove dalla cima della pila. Quindi le nostre nuove pile appariranno come:

       -3
  0  0  0
  ^

(([()()()])(()))
          ^

Questo ha senso; [...]fa negazione, quindi ]dovrebbe sottrarre verso il basso.

Ora dobbiamo eseguire un ). Come probabilmente ricorderai )è il posto nel programma in cui le cose vengono spinte nello stack, quindi sposteremo la parte superiore del terzo stack nello stack corrente, inoltre aggiungeremo l' -3elemento successivo al terzo stack.

 -3  0 -3
  ^

(([()()()])(()))
           ^

Ancora una volta incontriamo una delle nostre tre parentesi graffe aperte, quindi aggiungeremo un altro elemento al nostro terzo stack.

        0
 -3  0 -3
  ^

(([()()()])(()))
            ^

Come abbiamo detto in precedenza ()aumenterà la parte superiore del nostro terzo stack di uno.

        1
 -3  0 -3
  ^

(([()()()])(()))
              ^

E )si muoverà la parte superiore del Terzo Stack nello stack e aggiungere il basso attivi

  1
 -3  0 -2
  ^

(([()()()])(()))
               ^

L'ultimo )sposta il Terzo Stack nello stack attivo e poiché non ci sono elementi rimasti nel Terzo Stack da aggiungere, non fa altro.

 -2
  1
 -3  0
  ^

(([()()()])(()))
                ^

Il programma è finito, quindi terminiamo e produciamo.


Questo esempio ha lo scopo di darti un'idea di ciò che è e fa la terza pila. Non include tutte le operazioni, ma si spera che tu possa capire cosa ciascuna di esse fa da sola. Se stai ancora lottando, ho incluso un "cheatsheet" in fondo a questa risposta per aiutarti.

Ok, allora?

Ok, ora capisci il terzo stack, ma "E allora?" Lo stavi già utilizzando anche se non lo chiamavi "Terzo Stack", in che modo pensare in termini di Terzo Stack ti aiuta a giocare a golf?

Vediamo un problema. Vuoi prendere il triangolo di un numero . Questa è la somma di tutti i numeri inferiori a n.

Un approccio potrebbe essere quello di creare un accumulatore fuori dallo stack e aggiungerlo durante il conto alla rovescia. Questo crea un codice simile al seguente:

(<>)<>{(({}[()])()<>{})<>}{}<>({}<>)

Provalo online!

Questo codice è piuttosto compatto e si potrebbe pensare che non possa essere molto più piccolo. Tuttavia, se lo affrontiamo da un terzo punto di vista dello stack, diventa chiaro che questo è gravemente inefficiente. Invece di mettere il nostro accumulatore nello stack, possiamo metterlo sul terzo stack con a (e recuperarlo alla fine che usiamo ). Riprogrammeremo ancora una volta tutti i numeri, ma questa volta non dobbiamo fare molto per aumentare il nostro terzo stack, il programma lo fa per noi. Questo sembra:

({()({}[()])}{})

Provalo online

Questo codice ha meno della metà delle dimensioni della versione abbastanza ben giocata prima. In effetti, una ricerca al computer ha dimostrato che questo programma è il programma più breve possibile in grado di eseguire questa attività. Questo programma può essere spiegato usando l'approccio "somma di tutte le esecuzioni", ma penso che sia molto più intuitivo e chiaro quando spiegato con un approccio di terzo stack.

Quando uso il terzo stack?

Idealmente, ogni volta che inizi a lavorare su un nuovo problema in Brain-Flak, dovresti pensare a te stesso come farei pensando alla terza pila. Tuttavia, come regola generale ogni volta che devi tenere traccia di un qualche tipo di accumulatore o avere un totale parziale, è una buona idea provare a metterlo sul tuo terzo stack anziché sulle due pile reali.

Un'altra volta che potrebbe essere una buona idea considerare l'utilizzo del terzo stack è quando non si ha spazio per memorizzare un valore sugli altri due stack. Ciò può essere particolarmente utile quando si eseguono manipolazioni su due stack esistenti e si desidera salvare un valore per un uso successivo senza dover tenere traccia di dove si trova.

Limitazioni del terzo stack

Il terzo stack è molto potente in molti modi, ma ha i suoi limiti e svantaggi.

Innanzitutto, l'altezza massima della pila per la terza pila in un dato punto viene determinata al momento della compilazione. Ciò significa che se si desidera utilizzare una quantità di spazio nello stack, è necessario allocare tale spazio durante la scrittura del programma.

In secondo luogo, il terzo stack non è un accesso casuale. Ciò significa che non è possibile eseguire alcuna operazione su alcun valore ma sul valore più elevato. Inoltre, non è possibile spostare valori nello stack (diciamo scambiare i primi due elementi).

Conclusione

Il terzo stack è uno strumento potente e lo considero essenziale per ogni utente Brain-Flak. Ci vuole un po 'per abituarsi e richiede un cambiamento nel modo in cui pensi alla programmazione in Brain-Flak, ma se usato correttamente fa la differenza tra un discreto e uno straordinario quando si tratta di golf.

Cheatsheet

Ecco un elenco di operazioni e di come influenzano il terzo stack

 Operation  |                 Action
====================================================
   (,[,<    | Put a zero on top of the Third Stack
----------------------------------------------------
     )      | Add the top of the Third Stack to the
            | second element and move it to the 
            | active stack
----------------------------------------------------
     ]      | Subtract the top of the Third Stack
            | from the second element and pop it
----------------------------------------------------
     >      | Pop the top of the Third Stack
----------------------------------------------------
     ()     | Add one to the top of the Third Stack
----------------------------------------------------
     {}     | Pop the top of the active stack and
            | add it to the top of the Third Stack
----------------------------------------------------
     []     | Add the stack height to the Third
            | Stack
----------------------------------------------------
   <>,{,}   | Nothing

1
Wow, fantastico consiglio! In realtà stavo solo venendo qui per scrivere una risposta simile quando ho visto questo. +1! Preferisco pensarlo come l'accumulatore , ma la metafora del terzo stack è davvero interessante.
DJMcMayhem

L'ho sempre chiamato "spazio di lavoro" o "workstack".
sagiksp,

Nella versione TIO di Brainflak, {...}restituisce la somma di tutte le iterazioni.
Calcolatrice

@CalculatorFeline Sì, questo è vero su quasi tutte le versioni di Brain-Flak, tranne alcune molto antiche. Tuttavia non sono sicuro del motivo per cui hai commentato questo post in particolare.
Wheat Wizard

<>,{,} | Nothing
Calcolatrice

21

Trovare modulo / resto

Trovare n modulo m è una delle operazioni aritmetiche di base, importante per molte sfide. Per i casi m> 0 e n> = 0 , è possibile utilizzare il seguente frammento di 46 byte. Presuppone che n sia in cima allo stack attivo con m il successivo in basso e li sostituisce con n mod m . Lascia intatto il resto delle pile.

({}(<>))<>{(({})){({}[()])<>}{}}{}<>([{}()]{})

Questa versione annotata mostra il contenuto dello stack in alcuni punti del programma. ;separa le due pile e .segna la pila attiva.

. n m;
({}(<>))<>
{   . m; r 0   (r = n - km)
    (({}))
    . m m; r 0
    {({}[()])<>}
    {}
}
m-n%m-1 m; . 0
{}<>([{}()]{})
. n%m;

Mi ci è voluto un po 'per capire cosa facesse la parte non annotata ( {({}[()])<>}), ma una volta che l'ho capito ... Genius :-)
ETHproductions

11

Ridondanza push-pop

Questo è grande. È anche un po 'sfumato.

L'idea è che se spingi qualcosa e poi lo fai scoppiare senza fare nulla, non avresti dovuto spingerlo affatto.

Ad esempio, se si desidera spostare qualcosa nello stack e quindi aggiungerne uno, è possibile scrivere il seguente codice:

({}<>)({}())

Questo può essere più semplice in questo modo:

({}<>())

Il primo programma raccoglie l'oggetto lo sposta, lo raccoglie di nuovo e ne aggiunge uno, mentre il secondo fa entrambi in un colpo solo.

Quell'esempio è stato semplice, tuttavia può diventare molto più complesso. Prendi ad esempio:

({}<({}<>)><>)(<((()()()){}[((){}{})])>)

La riduzione è meno chiara qui, ma il 4 ° pop nel programma può essere ridotto con la 2a spinta, in questo modo:

(<((()()()){}[((){}<({}<>)><>{})])>)

Questo è lo strumento più potente nel mio repertorio di golf ma ci vuole un po 'di pratica per riuscire a farlo. Dopo averlo fatto per un po 'sarai in grado di individuarli quasi istantaneamente.


Nell'ultimo esempio, la parte nella prima coppia di parentesi non equivale a ({}<{}>)?
feersum

@feersum No, non lo è. Sposta una copia del secondo oggetto nello stack nello stack mentre lo ({}<{}>)distrugge completamente. Tuttavia, questi programmi non erano pensati per essere ottimali solo per evidenziare il principio al lavoro qui.
Wheat Wizard

6

Ottimizza i tuoi numeri interi

I numeri interi sono noiosi da rappresentare in Brain-Flak. Fortunatamente abbiamo una domanda che aiuterà Golf a Brain-Flak Integer per te. (Si noti che la domanda è progettata per spingere l'intero nella pila, quindi la ridondanza push-pop probabilmente si applica a programmi più realistici.)


Nota che abbiamo anche brain-flak.github.io/integer/ , che esegue uno di quegli algoritmi online per comodità.
DJMcMayhem

@DrMcMoylex Tutto ciò di cui abbiamo bisogno ora come nell'implementazione del metagolfer intero nello stesso Brain-Flak!
Neil,


5

Spingere i contatori di loop extra

Spesso vorrai fare qualcosa del genere

Esegue l' operazione X su ogni elemento nello stack

o

Esegui l' operazione X su ogni coppia di elementi adiacenti nello stack

Quando l'ingresso può contenere "0" o il risultato dell'operazione X può dare uno 0, questo è davvero scomodo. Perché dovrai fare:

([])
{

  {}

  ({}...<>)
  ([])

}

Per fare X per ogni elemento, e successivamente

<>
([])
{
  {}
  ({}<>)
  <>
  ([])
}
<>

Per invertire nuovamente l'array.

Ancora peggio, se stiamo operando su coppie di elementi adiacenti, dovremo fare ([][()])al posto di ([]). Questo è davvero scomodo. Ecco il trucco: mentre stai facendo X per ogni elemento, spingi un 1 sopra lo stack alternativo proprio sopra il X(element). Quindi, mentre lo stai invertendo, puoi semplicemente farlo

<>
{
  {}
  ({}<>)
  <>
}
<>

Si tratta di 8 byte in meno, quindi quando si inserisce il codice aggiuntivo per spingere 1, si finisce per salvare 4-6 byte. Per un esempio più concreto, confrontare l'attività di ottenere delta di un array. Senza questo trucco, avresti bisogno di:

([][()]){

    {}

    ([{}]({})<>)<>

    ([][()])

}{}{}<>

([])
{{}({}<>)<>([])}<>

Per 62. Con questo trucco, avrai

([][()]){

    {}

    ([{}]({})<>)(())<>

    ([][()])

}{}{}<>

{{}({}<>)<>}<>

Per 58. Se usato nel modo giusto (ad esempio, invertendo prima e rimuovendo due ([][()])da dopo), questo potrebbe risparmiare ancora di più in scenari specifici.


3

Approfitta del nilad 'Stack-Height'

Soprattutto nelle o sfide in cui si conosce sempre la dimensione dell'input, è possibile sfruttare il nilad 'Stack-Height'[] per creare numeri interi.

Lavoriamo su questo con un'ipotetica sfida: l'output CAT. Il modo non golfoso è quello di utilizzare il giocatore di golf intero online per spingere 67, 65 e 84. Questo dà:

(((((()()()()){}){}){}()){}())

(((((()()()()){}){}){}){}())

((((((()()()){}()){}){})){}{})

(Newline per chiarezza). Si tratta di 88 byte e non eccezionale. Se invece spingiamo le differenze consecutive tra i valori, possiamo risparmiare molto. Quindi avvolgiamo il primo numero in una chiamata push e sottraggiamo 2:

(   (((((()()()()){}){}){}()){}())  [()()] )

Quindi, prendiamo questo codice, lo racchiudiamo in una chiamata push e aggiungiamo 19 alla fine:

(  ((((((()()()()){}){}){}()){}())[()()])   ((((()()()){})){}{}()) )

Si tratta di 62 byte, per un enorme golf da 26 byte!

Ora qui è dove possiamo sfruttare il nilad dello stack-height. Quando inizieremo a spingere 19, sappiamo che ci sono già 2 oggetti in pila, quindi []valuteremo 2. Possiamo usarlo per creare un 19 in meno byte. Il modo più ovvio è cambiare l'interno ()()()in ()[]. Ciò consente di risparmiare solo due byte. Con un po 'di armeggiamento, risulta che possiamo spingere 19 con

((([][]){})[]{})

Questo ci fa risparmiare 6 byte. Ora siamo scesi a 56.

Puoi vedere questo suggerimento utilizzato in modo molto efficace su queste risposte:


Il tuo CATprogramma effettivamente spinge TAC: P
Wheat Wizard

Inoltre, 52 byte
Wheat Wizard

2
So che questo è un suggerimento ma non posso fare a meno di me stesso, 50 byte .
Wheat Wizard

1
Un altro suggerimento strano ma a volte efficace per aiutare l'abuso []: anteporre 0s con (<>)s prima del codice. Funziona davvero solo se stai pianificando di invertire il codice comunque, ma se trovi il numero giusto puoi ridurre abbastanza il tuo codice. Un esempio piuttosto estremo in cui aggiungo 6 0s, che mi permette di usare tutti gli []s che uso()
Jo King,

2

Usa il wiki

Abbiamo una wiki ! Ha alcune mancanze, ma è una risorsa utile. Ha elenchi di programmi utili, ben equipaggiati, puliti in pila che puoi incollare nel tuo codice. Se vuoi fare qualcosa che pensi che qualcuno potrebbe aver fatto prima che ci siano buone probabilità che sia sul wiki.


2

Loop migliore

Eccone uno semplice:

Un costrutto abbastanza comune è:

(({})<{({}[()]<...>)}{}>)

Dove vuoi fare il ciclo n volte ma mantenendo comunque il n. Tuttavia, questo può essere scritto come:

({<({}[()]<...>)>()}{})

per salvare 2 byte.

Un altro paradigma abbastanza comune è

([])({<{}>...<([])>}{})

Che eseguirà il loop e accumulerà l'intero stack. A causa di alcuni fantasiosi calcoli è lo stesso di:

(([]){[{}]...([])}{})

1

Controlla i tuoi aspetti negativi

A volte puoi giocare a golf a pochi byte scegliendo strategicamente cosa negare con la [...]monade.

Un semplice esempio è in [...]s nidificati . Ad esempio [()()()[()()]]potrebbe essere[()()()]()()

Supponi di voler verificare se un valore è una delle parentesi iniziali (<{[. Il tentativo iniziale è quello di spingere la differenza tra ogni personaggio e passare sopra la sottrazione

({}<(((((       #Push the differences between start bracket characters
(((()()()()){}){}){})   #Push 32
[()])                   #Push 31
[((()()())()){}{}])     #Push 20
){})><>)                #Push 40
<>({<(([{}]<>{}))>(){[()](<{}>)}<>})

Tuttavia, puoi salvare 4 byte premendo invece le versioni negative delle differenze!

({}<(((((       #Push the differences between start bracket characters
((([()()()()]){}){}){}) #Push -32
())                     #Push -31
((()()())()){}{})       #Push -20
){})><>)                #Push -40
<>({<(({}<>{}))>(){[()](<{}>)}<>})

In generale, questo non ti risparmia molto, ma costa anche pochissimo sforzo per cambiare ciò che [...]è circostante. Fai attenzione alle situazioni in cui puoi spingere il negativo di un contatore invece del positivo per risparmiare sull'incremento più volte invece che sul decremento in seguito. O scambiare (a[b])con ([a]b)per rendere la differenza tra due numeri negativi anziché positivi.


1
Cose simili possono essere fatte con zeri <a<b>c>-> <abc>e <a[b]c>-> <abc>.
Wheat Wizard
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.