Consejos para jugar golf en Brain-Flak

24

Brain-flak es un lenguaje turing-tarpit basado en pila, escrito en colaboración entre yo, DJMcMayhem y 1000000000 .

Algunos usuarios tienen mucha experiencia en las formas misteriosas de Brain-Flak. Así que pensé que era una buena idea plantear esta pregunta como una forma para nosotros, y con suerte también para otros, compartir nuestro conocimiento con la comunidad y reducir la barrera de entrada a este lenguaje "diseñado para ser doloroso de usar". Y tal vez incluso nos enseñe a aquellos de nosotros con más experiencia algunos trucos nuevos.

Entonces, ¿qué consejos tienes para jugar al golf en Brain Flak? Estoy buscando ideas que se puedan aplicar a los problemas de golf de código en general que sean al menos algo específicos para el ataque cerebral (por ejemplo, "eliminar comentarios" no es una respuesta).

Por favor, publique un consejo por respuesta.

Asistente de trigo
fuente

Respuestas:

22

Usa la tercera pila

Si ha leído el título, puede estar un poco confundido. ¿Seguramente solo hay dos pilas en Brain-Flak? Sin embargo, le aseguro que existe y que es una de las herramientas más poderosas, si no la más poderosa, para escribir y jugar Brain-Flak.


¿Qué es la "tercera pila"?

Cada programa Brain-Flak usa la tercera pila de una forma u otra, pero la mayor parte del uso se lleva a cabo detrás de escena y a menudo es útil simplemente ignorar el hecho de que existe. Cada paréntesis en el programa agrega o elimina un elemento de la pila. Tres de las llaves abiertas ([<agregan un elemento a la pila, mientras que sus tres conjugados )]>eliminan un elemento de la pila. El valor del elemento en la pila es el valor del alcance actual del programa y el uso de nilads modificará este valor de ciertas maneras. El paréntesis cerrado )tiene la función única de mover un elemento de la tercera pila a la pila actual; un empujón.

Esperemos que esto te resulte claro. La tercera pila es una especie de pila que recuerda los valores de retorno del código que ya se ha ejecutado. Veamos un ejemplo de un programa simple que realiza un seguimiento de las dos pilas normales y la tercera pila.

Ejemplo

Recorreremos el siguiente programa. Este programa empuja -3, 1, -2a la pila.

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

Comenzamos con tres llaves abiertas, que empujan un cero a la tercera pila.

Nuestras pilas ahora se ven así, la tercera pila es la de la derecha y la pila activa tiene ^debajo:

        0
        0
  0  0  0
  ^

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

Ahora tenemos tres ()nilas. Estos no hacen nada a las dos pilas normales, sin embargo, cada una agrega una a la parte superior de la tercera pila haciendo que nuestras pilas se vean así:

        3
        0
  0  0  0
  ^

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

Ahora encontramos que, ]como se indicó antes, las llaves cerradas eliminan un elemento de la tercera pila, pero ]tiene la función de restar el elemento que elimina de la parte superior de la pila. Así nuestras nuevas pilas se verán así:

       -3
  0  0  0
  ^

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

Esto tiene sentido; [...]hace negación por lo que ]debería restar hacia abajo.

Ahora debemos ejecutar a ). Como probablemente recuerde, )es el lugar en el programa donde las cosas se envían a la pila, por lo que moveremos la parte superior de la tercera pila a la pila actual, además agregaremos el -3elemento siguiente en la tercera pila.

 -3  0 -3
  ^

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

Una vez más nos encontramos con una de nuestras tres llaves abiertas, por lo que agregaremos otro elemento a nuestra tercera pila.

        0
 -3  0 -3
  ^

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

Como dijimos anteriormente, ()aumentará la parte superior de nuestra tercera pila en uno.

        1
 -3  0 -3
  ^

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

Y )moverá la parte superior de la tercera pila a la pila activa y agregará hacia abajo

  1
 -3  0 -2
  ^

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

El último )mueve la tercera pila a la pila activa y, dado que no quedan elementos en la tercera pila para agregar, no hace nada más.

 -2
  1
 -3  0
  ^

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

El programa ha terminado, así que terminamos y damos salida.


Este ejemplo tiene la intención de darle una idea de lo que es y hace el Third Stack. No incluye todas las operaciones, pero es de esperar que pueda descubrir qué hace cada una por sí solo. Si todavía tiene dificultades, he incluido una "hoja de trucos" en la parte inferior de esta respuesta para ayudarlo.

¿OK y eso qué?

Ok, ahora entiendes la tercera pila, pero "¿Y qué?" Ya lo estaba utilizando, incluso si no lo llamaba "Third Stack", ¿cómo le ayuda el golf en términos de Third Stack?

Veamos un problema. Desea tomar el triángulo de un número . Esta es la suma de todos los números menores que n.

Un enfoque podría ser crear un acumulador en la pila y agregarlo a medida que realiza la cuenta regresiva. Esto crea un código que se ve así:

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

Pruébalo en línea!

Este código es bastante compacto y uno podría pensar que no puede ser mucho más pequeño. Sin embargo, si lo abordamos desde un punto de vista de la tercera pila, queda claro que esto es extremadamente ineficiente. En lugar de poner nuestro acumulador en la pila, podemos colocarlo en la tercera pila con un (y recuperarlo al final que usamos ). Una vez más, recorreremos todos los números, pero esta vez no tenemos que hacer nada para incrementar nuestra tercera pila, el programa lo hace por nosotros. Esto se ve así:

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

Pruébalo en línea

Este código es menos de la mitad del tamaño de la versión bastante bien desarrollada que hicimos antes. De hecho, una búsqueda por computadora ha demostrado que este programa es el programa más corto posible que puede realizar esta tarea. Este programa se puede explicar utilizando el enfoque de "suma de todas las ejecuciones", pero creo que es mucho más intuitivo y claro cuando se explica utilizando un enfoque de Third Stack.

¿Cuándo uso la tercera pila?

Idealmente, cada vez que comience a trabajar en un nuevo problema en Brain-Flak, debe pensar cómo haría esto con la Tercera Pila en mente. Sin embargo, como regla general, siempre que tenga que realizar un seguimiento de algún tipo de acumulador o tenga un total acumulado, es una buena idea intentar colocarlo en su tercera pila en lugar de las dos pilas reales.

Otra vez que podría ser una buena idea considerar usar su tercera pila es cuando no tiene espacio para almacenar algún valor en las otras dos pilas. Esto puede ser particularmente útil cuando realiza manipulaciones en dos pilas existentes y desea guardar un valor para su uso posterior sin tener que realizar un seguimiento de dónde está.

Limitaciones de la tercera pila

El Third Stack es muy poderoso en muchos sentidos, pero viene con sus propias limitaciones e inconvenientes.

En primer lugar, la altura máxima de la pila para la tercera pila en cualquier punto determinado se determina en el momento de la compilación. Esto significa que si desea utilizar una cantidad de espacio en la pila, debe asignar ese espacio cuando está escribiendo el programa.

En segundo lugar, la tercera pila no es el acceso aleatorio. Esto significa que no puede realizar ninguna operación en ningún valor que no sea el valor más alto. Además, no puede mover valores en la pila (por ejemplo, intercambie los dos primeros elementos).

Conclusión

The Third Stack es una herramienta poderosa y lo consideraría esencial para todos los usuarios de Brain-Flak. Toma un tiempo acostumbrarse y requiere un cambio en la forma en que piensas sobre la programación en Brain-Flak, pero cuando se usa correctamente, hace toda la diferencia entre un juego decente y uno increíble cuando se trata de jugar al golf.

Hoja de trucos

Aquí hay una lista de operaciones y cómo afectan la tercera pila

 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
Asistente de trigo
fuente
1
¡Guau, un consejo fantástico! En realidad, solo venía aquí para escribir una respuesta similar cuando vi esto. +1! Prefiero pensar en él como The Accumulator , pero la metáfora de The Third Stack es realmente interesante.
DJMcMayhem
Siempre lo llamé "espacio de trabajo" o "pila de trabajo".
sagiksp
En la versión TIO de Brainflak, {...}devuelve la suma de todas las iteraciones.
CalculatorFeline
@CalculatorFeline Sí, esto es cierto en casi todas las versiones de Brain-Flak, excepto algunas muy tempranas. Sin embargo, no estoy seguro de por qué has comentado eso en esta publicación en particular.
Wheat Wizard
<>,{,} | Nothing
CalculatorFeline
21

Encontrar módulo / resto

Encontrar n módulo m es una de las operaciones aritméticas básicas, importante para muchos desafíos. Para los casos m> 0 y n> = 0 , el siguiente fragmento de 46 bytes puede ser utilizado. Se supone que n está en la parte superior de la pila activa con m el siguiente hacia abajo, y los reemplaza con n mod m . Deja el resto de las pilas intactas.

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

Esta versión anotada muestra el contenido de la pila en algunos puntos del programa. ;separa las dos pilas y .marca la pila activa.

. n m;
({}(<>))<>
{   . m; r 0   (r = n - km)
    (({}))
    . m m; r 0
    {({}[()])<>}
    {}
}
m-n%m-1 m; . 0
{}<>([{}()]{})
. n%m;
Feersum
fuente
Me tomó un tiempo entender lo que hizo la parte no anotada ( {({}[()])<>}), pero una vez que lo descubrí ... Genio :-)
ETHproductions
11

Redundancia Push-Pop

Este es un grande. También es un poco matizado.

La idea es que si presiona algo y luego lo hace estallar sin hacer nada, no debería haberlo presionado.

Por ejemplo, si desea mover algo a la pila y luego agregar uno, puede escribir el siguiente código:

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

Esto puede ser más simple así:

({}<>())

El primer programa recoge el elemento, lo mueve, lo recoge de nuevo y agrega uno, mientras que el segundo hace las dos cosas de una sola vez.

Ese ejemplo fue simple, sin embargo, puede volverse mucho más complejo. Toma por ejemplo:

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

La reducción es menos clara aquí, pero el 4º pop en el programa se puede reducir con el 2º empuje, así:

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

Esta es la herramienta más poderosa en mi repertorio de golf, pero requiere algo de práctica para ser bueno en eso. Una vez que haya estado haciendo esto por un tiempo, podrá detectarlo casi al instante.

Asistente de trigo
fuente
En el último ejemplo, ¿no es equivalente la parte del primer par de paréntesis ({}<{}>)?
feersum el
@feersum No, no lo es. Mueve una copia del segundo elemento en la pila a la pila fuera de la pila mientras ({}<{}>)destruye el elemento por completo. Sin embargo, estos programas no estaban destinados realmente a ser óptimos solo para resaltar el principio en el trabajo aquí.
Wheat Wizard
6

Optimiza tus enteros

Los enteros son tediosos de representar en Brain-Flak. Afortunadamente tenemos una pregunta que ayudará a Golf a un Brain-Flak Integer para usted. (Tenga en cuenta que la pregunta está diseñada para empujar el entero a la pila, por lo que la redundancia push-pop probablemente se aplica a programas más realistas).

Neil
fuente
Tenga en cuenta que también tenemos brain-flak.github.io/integer/ , que ejecuta uno de esos algoritmos en línea para mayor comodidad.
DJMcMayhem
@DrMcMoylex ¡Todo lo que necesitamos ahora como implementación del metagolfer entero en Brain-Flak!
Neil
1
Tenemos eso codegolf.stackexchange.com/a/98054/31716 : D
DJMcMayhem
5

Empuje contadores de bucle adicionales

Con frecuencia, querrás hacer algo como

Realice la operación X en cada elemento de la pila

o

Realice la operación X en cada par de elementos adyacentes en la pila

Cuando la entrada puede contener '0', o el resultado de la operación X puede dar un 0, esto es realmente inconveniente. Porque deberás hacer:

([])
{

  {}

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

}

Para hacer X a cada elemento, y luego

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

Para revertir la matriz de nuevo.

Peor aún, si estamos operando en pares de elementos adyacentes, tendremos que hacerlo ([][()])en lugar de ([]). Esto es realmente inconveniente. Aquí está el truco: mientras haces X para cada elemento, empuja un 1 sobre la pila alternativa justo encima del X(element). Luego, mientras lo inviertes, simplemente puedes hacer

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

Esto es 8 bytes más corto, por lo que cuando factoriza el código adicional para presionar 1, terminará ahorrando 4-6 bytes. Para un ejemplo más concreto, compare la tarea de obtener deltas de una matriz. Sin este truco, necesitarías:

([][()]){

    {}

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

    ([][()])

}{}{}<>

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

Por 62. Con este truco, tendrás

([][()]){

    {}

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

    ([][()])

}{}{}<>

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

Para 58. Si se usa de la manera correcta (por ejemplo, retroceder primero y eliminar dos ([][()])de más adelante), esto podría ahorrar aún más en escenarios específicos.

DJMcMayhem
fuente
3

Aproveche la nilad 'Stack-Height'

Especialmente en los , o desafíos en los que siempre conoce el tamaño de la entrada, puede aprovechar la nilad 'Altura de pila' []para crear enteros.

Analicemos esto con un desafío hipotético: la salida CAT. La forma que no es de golf es usar el jugador de golf entero en línea para empujar 67, 65 y 84. Esto proporciona:

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

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

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

(Nuevas líneas para mayor claridad). Esto es 88 bytes, y no tan bueno. Si, en cambio, empujamos las diferencias consecutivas entre los valores, podemos ahorrar mucho. Entonces envolvemos el primer número en un llamada push y restamos 2:

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

Luego, tomamos este código, lo envolvemos en un llamada push y agregamos 19 al final:

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

¡Esto es 62 bytes, para un enorme golf de 26 bytes!

Ahora aquí es donde podemos aprovechar el nilad de altura de pila. Cuando comencemos a presionar 19, sabemos que ya hay 2 elementos en la pila, por []lo que evaluaremos 2. Podemos usar esto para crear un 19 en menos bytes. La forma obvia es cambiar lo interno ()()()a()[] . Sin embargo, esto solo ahorra dos bytes. Con algunos ajustes más, resulta que podemos empujar 19 con

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

Esto nos ahorra 6 bytes. Ahora tenemos 56.

Puede ver que este consejo se usa de manera muy efectiva en estas respuestas:

DJMcMayhem
fuente
Su CATprograma realmente empuja TAC: P
Wheat Wizard
Además, 52 bytes
Wheat Wizard
2
Sé que esto es un consejo, pero no puedo evitarlo, 50 bytes .
Wheat Wizard
1
Otro consejo extraño pero a veces efectivo para ayudar al abuso []: anteponer 0s con (<>)s antes de su código. De todos modos, esto solo funciona si planea empujar el código en reversa de todos modos, pero si encuentra el número correcto, puede reducir su código bastante. Un ejemplo bastante extremo en el que agrego 6 0s, que me permite usar tantos []s como yo uso()
Jo King
2

Usa la wiki

¡Tenemos una wiki ! Tiene algunas deficiencias, pero es un recurso útil. Tiene listas de programas útiles, bien diseñados y apilados que puede pegar en su código. Si quieres hacer algo, crees que alguien podría haberlo hecho antes de que haya una buena posibilidad de que esté en la wiki.

Asistente de trigo
fuente
2

Mejor bucle

Aquí hay una fácil:

Una construcción bastante común es:

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

Donde desea recorrer n veces pero aún mantener la n. Sin embargo, esto se puede escribir como:

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

para guardar 2 bytes.

Otro paradigma bastante común es

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

Lo que hará un bucle y acumulará toda la pila. Debido a algunas matemáticas elegantes, esto es lo mismo que:

(([]){[{}]...([])}{})
Asistente de trigo
fuente
1

Revisa tus negativos

A veces puede jugar al golf unos pocos bytes eligiendo estratégicamente qué negar con el [...] mónada.

Un ejemplo simple está en [...]s anidados . Por ejemplo[()()()[()()]] podría ser[()()()]()()

Supongamos que desea verificar si un valor es uno de los corchetes iniciales (<{[. El intento inicial es empujar la diferencia entre cada personaje y recorrerlo para restarlo

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

Sin embargo, ¡puede ahorrar 4bytes presionando las versiones negativas de las diferencias!

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

En general, esto no te ahorra mucho, pero también cuesta muy poco esfuerzo cambiar lo que [...]está rodeando. Esté atento a las situaciones en las que puede empujar el negativo de un contador en lugar del positivo para ahorrar en incrementos múltiples veces en lugar de disminuir más adelante. O intercambiando (a[b])con los ([a]b)que hacen la diferencia entre dos números negativos en lugar de positivos.

Jo King
fuente
1
Se pueden hacer cosas similares con ceros <a<b>c>-> <abc>y <a[b]c>-> <abc>.
Wheat Wizard