Dicas para jogar golfe em Brain-Flak


24

Brain-flak é uma linguagem turing-tarpit baseada em pilha, escrita em colaboração entre mim, DJMcMayhem e 1000000000 .

Alguns usuários são muito experientes nas formas misteriosas do Brain-Flak. Por isso, achei uma boa ideia fazer essa pergunta como uma maneira de compartilharmos nosso conhecimento com a comunidade e, esperançosamente, de outros também, e diminuir a barra de entrada para esse idioma "projetado para ser doloroso de usar". E talvez até ensine a nós, com mais experiência, alguns novos truques.

Então, que dicas você tem para jogar golfe no cérebro? Estou procurando idéias que possam ser aplicadas para codificar problemas de golfe em geral que sejam pelo menos um pouco específicos para ataques cerebrais (por exemplo, "remover comentários" não é uma resposta).

Poste uma dica por resposta.

Respostas:


22

Use a terceira pilha

Se você leu o título, pode ficar um pouco confuso. Certamente existem apenas duas pilhas no Brain-Flak? No entanto, garanto que ele existe e é uma das ferramentas mais poderosas, se não a mais poderosa, para escrever e jogar no Brain-Flak.


O que é o "Third Stack"?

Todo programa Brain-Flak usa a terceira pilha de uma maneira ou de outra, mas a maior parte do uso é realizada nos bastidores e geralmente é útil simplesmente ignorar o fato de que ela existe. Cada parêntese no programa adiciona ou remove um item da pilha. Três dos chavetas abertos ([<adicionam um item à pilha, enquanto os três conjugados )]>removem um item da pilha. O valor do item na pilha é o valor do escopo atual do programa e o uso de nilads modificará esse valor de determinadas maneiras. O parêntese próximo )tem a função exclusiva de mover um elemento da Terceira Pilha para a pilha atual; um empurrão.

Espero que isso esteja ficando claro para você. A Terceira Pilha é algum tipo de pilha que lembra os valores de retorno do código que já foi executado. Vamos seguir um exemplo de um programa simples, acompanhando as duas pilhas normais e a Terceira Pilha.

Exemplo

Seguiremos o seguinte programa. Este programa envia -3, 1, -2para a pilha.

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

Começamos com três chaves abertas, que empurram o zero para a terceira pilha.

Nossas pilhas agora se parecem com isso, a Terceira Pilha é a da direita e a pilha ativa tem uma parte ^inferior:

        0
        0
  0  0  0
  ^

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

Agora temos três ()niladas. Isso não faz nada com as duas pilhas normais, no entanto, cada uma adiciona uma ao topo da Terceira Pilha, fazendo com que nossas pilhas se pareçam com:

        3
        0
  0  0  0
  ^

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

Agora encontramos um, ]conforme declarado antes, que chaves fechadas removem um item da Terceira Pilha, mas ]tem a função de subtrair o elemento que ele remove da parte superior da pilha. Assim, nossas novas pilhas terão a seguinte aparência:

       -3
  0  0  0
  ^

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

Isso faz sentido; [...]negação ]deve subtrair para baixo.

Agora devemos executar a ). Como você provavelmente se lembra, )é o local do programa em que as coisas são empurradas para a pilha, de modo que moveremos o topo da Terceira Pilha para a pilha atual, além disso, adicionaremos o -3elemento ao próximo na terceira pilha.

 -3  0 -3
  ^

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

Mais uma vez encontramos um de nossos três chavetas abertas, para adicionar outro elemento à nossa Terceira Pilha.

        0
 -3  0 -3
  ^

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

Como dissemos anteriormente (), aumentará o topo da nossa terceira pilha em um.

        1
 -3  0 -3
  ^

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

E )moverá o topo da Terceira Pilha para a pilha ativa e adicionará para baixo

  1
 -3  0 -2
  ^

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

O último )move a Terceira Pilha para a pilha ativa e, como não há elementos restantes na Terceira Pilha para adicionar, não faz mais nada.

 -2
  1
 -3  0
  ^

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

O programa terminou, então finalizamos e produzimos.


Este exemplo tem como objetivo dar uma idéia do que a Terceira Pilha é e faz. Ele não inclui todas as operações, mas espero que você possa descobrir o que cada uma delas faz por conta própria. Se você ainda está com dificuldades, incluí uma "folha de dicas" na parte inferior desta resposta para ajudá-lo.

Tá, e daí?

Ok, agora você entende a Terceira Pilha, mas "E daí?" Você já o estava usando, mesmo que não o chamasse de "Terceiro Stack", como o pensamento em termos do Third Stack o ajuda a jogar golfe?

Vamos olhar para um problema. Você quer pegar o triângulo de um número . Esta é a soma de todos os números menores que n.

Uma abordagem pode ser criar um acumulador no offstack e adicioná-lo à medida que você faz a contagem regressiva. Isso cria um código que se parece com isso:

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

Experimente Online!

Este código é bastante compacto e pode-se pensar que não pode ficar muito menor. No entanto, se o abordarmos do terceiro ponto de vista da pilha, fica claro que isso é grosseiramente ineficiente. Em vez de colocar nosso acumulador na pilha, podemos colocá-lo na terceira pilha com um (e recuperá-lo no final que usamos ). Mais uma vez, percorreremos todos os números, mas desta vez não precisamos fazer muito para incrementar nossa Terceira Pilha, o programa faz isso por nós. Isso se parece com:

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

Experimente Online

Esse código tem menos da metade do tamanho da versão bastante bem elaborada que fizemos anteriormente. De fato, uma pesquisa no computador comprovou que esse programa é o programa mais curto possível que pode executar essa tarefa. Este programa pode ser explicado usando a abordagem "soma de todas as execuções", mas acho que é muito mais intuitivo e claro quando explicado usando uma abordagem da Terceira Pilha.

Quando uso a Terceira Pilha?

Idealmente, sempre que você começar a trabalhar em um novo problema no Brain-Flak, pense em como eu faria isso com a Terceira Pilha em mente. No entanto, como regra geral, sempre que você tiver que rastrear algum tipo de acumulador ou ter um total em execução, é uma boa ideia tentar colocá-lo na sua terceira pilha, em vez das duas pilhas reais.

Outro momento em que pode ser uma boa ideia considerar o uso da terceira pilha é quando você não tem espaço para armazenar algum valor nas outras duas pilhas. Isso pode ser particularmente útil quando você está fazendo manipulações em duas pilhas existentes e deseja salvar um valor para uso posterior sem precisar acompanhar onde está.

Limitações da terceira pilha

O Third Stack é muito poderoso de várias maneiras, mas vem com suas próprias limitações e desvantagens.

Em primeiro lugar, a altura máxima da pilha para a terceira pilha em qualquer ponto é determinada em tempo de compilação. Isso significa que, se você quiser usar uma quantidade de espaço na pilha, precisará alocar esse espaço ao escrever o programa.

Em segundo lugar, a terceira pilha não é de acesso aleatório. Isso significa que você não pode executar nenhuma operação com nenhum valor, exceto o valor mais alto. Além disso, você não pode mover valores na pilha (por exemplo, troque os dois primeiros elementos).

Conclusão

O Third Stack é uma ferramenta poderosa e eu consideraria essencial para todos os usuários do Brain-Flak. Demora um pouco para se acostumar e requer uma mudança na maneira como você pensa sobre programação no Brain-Flak, mas quando usado corretamente, faz toda a diferença entre um decente e um incrível quando se trata de golfe.

Folha de dicas

Aqui está uma lista de operações e como elas afetam a Terceira Pilha

 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

11
Uau, dica fantástica! Na verdade, eu estava vindo aqui para escrever uma resposta semelhante quando vi isso. +1! Prefiro pensar nisso como o acumulador , mas a metáfora da terceira pilha é realmente interessante.
DJMcMayhem

Eu sempre o chamei de "espaço de trabalho" ou "pilha de trabalho".
3051717

Na versão TIO do Brainflak, {...}retorna a soma de todas as iterações.
CalculatorFeline

@CalculatorFeline Sim, isso é verdade em quase todas as versões do Brain-Flak, exceto em algumas muito antigas. No entanto, não sei por que você comentou isso neste post em particular.
Assistente de trigo

<>,{,} | Nothing
CalculatorFeline

21

Localizando módulo / restante

Encontrar n módulo m é uma das operações aritméticas básicas, importante para muitos desafios. Para os casos de m> 0 e n> = 0 , pode ser usado o seguinte fragmento de 46-byte. Ele assume que n está no topo da pilha ativa com m o próximo pressionado e os substitui por n mod m . Deixa o resto das pilhas intactas.

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

Esta versão anotada mostra o conteúdo da pilha em alguns pontos do programa. ;separa as duas pilhas e .marca a pilha ativa.

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

Levei um tempo para entender o que a parte não anotadas fez ( {({}[()])<>}), mas uma vez eu percebi isso ... Genius :-)
ETHproductions

11

Redundância Push-Pop

Este é um grande problema. Também é um pouco diferente.

A idéia é que, se você pressiona alguma coisa e a abre sem fazer nada, não deveria ter pressionado.

Por exemplo, se você quiser mover algo para o offstack e depois adicionar um, poderá escrever o seguinte código:

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

Isso pode ser mais simples assim:

({}<>())

O primeiro programa pega o item, move-o, pega-o novamente e adiciona um, enquanto o segundo faz os dois de uma só vez.

Esse exemplo foi simples, porém pode ficar muito mais complexo. Tomemos, por exemplo:

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

A redução é menos clara aqui, mas o quarto pop do programa pode ser reduzido com o segundo push, da seguinte forma:

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

Esta é a ferramenta mais poderosa do meu repertório de golfe, mas é preciso alguma prática para ser bom nisso. Depois de fazer isso por um tempo, você será capaz de identificá-lo quase instantaneamente.


No último exemplo, a parte do primeiro par de parênteses não é equivalente a ({}<{}>)?
feersum

@feersum Não, não é. Move uma cópia do segundo item da pilha para fora da pilha e ({}<{}>)destrói o item completamente. No entanto, esses programas não foram realmente concebidos para serem ótimos apenas para destacar o princípio em funcionamento aqui.
Wheat Wizard

6

Otimize seus números inteiros

Inteiros são tediosos para representar no Brain-Flak. Felizmente, temos uma pergunta que ajudará o Golf a um número inteiro Brain-Flak para você. (Observe que a pergunta foi projetada para enviar o número inteiro para a pilha, portanto a redundância push-pop provavelmente se aplica a programas mais realistas.)


Observe que também temos brain-flak.github.io/integer/ , que executa um desses algoritmos on-line por conveniência.
DJMcMayhem

@DrMcMoylex Tudo o que precisamos agora, como na implementação do metagolfer inteiro no próprio Brain-Flak!
Neil


5

Empurre contadores de loop extra

Freqüentemente, você desejará fazer algo como

Execute a operação X em todos os elementos da pilha

ou

Execute a operação X em cada par de elementos adjacentes na pilha

Quando a entrada pode conter '0' ou o resultado da operação X pode dar um 0, isso é realmente inconveniente. Porque você precisará fazer:

([])
{

  {}

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

}

Para executar X em cada elemento e depois

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

Para reverter a matriz novamente.

Pior ainda, se estivermos operando pares de elementos adjacentes, precisaremos fazer isso ([][()])no lugar de ([]). Isso é realmente inconveniente. Aqui está o truque: Enquanto você faz X em cada elemento, pressione 1 na pilha alternativa logo acima da X(element). Então, enquanto você está revertendo, você pode simplesmente fazer

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

Isso é 8 bytes mais curto; portanto, quando você levar em consideração o código extra para pressionar 1, acabará economizando de 4 a 6 bytes. Para um exemplo mais concreto, compare a tarefa de obter deltas de uma matriz. Sem esse truque, você precisaria de:

([][()]){

    {}

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

    ([][()])

}{}{}<>

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

Para 62. Com esse truque, você terá

([][()]){

    {}

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

    ([][()])

}{}{}<>

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

Para 58. Se usado da maneira correta (por exemplo, reverter primeiro e remover dois ([][()])depois), isso pode economizar ainda mais em cenários específicos.


3

Aproveite o nilad 'Stack-Height'

Especialmente em , ou desafios em que você sempre sabe o tamanho da entrada, você pode tirar vantagem da nilad 'Stack-Height'[] para criar números inteiros.

Vamos trabalhar com isso com um desafio hipotético: saída CAT. A maneira não-golfista é usar o jogador inteiro online para empurrar 67, 65 e 84. Isso fornece:

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

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

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

(Novas linhas para maior clareza). Isso é 88 bytes, e não é tão bom assim. Se, em vez disso, forçarmos as diferenças consecutivas entre os valores, podemos economizar muito. Então, nós envolvemos o primeiro número em um empurrão chamada, e subtrair 2:

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

Então, tomamos este código, envolvê-la em um empurrão chamada, e adicionar 19 para o final:

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

São 62 bytes, para um enorme golfe de 26 bytes!

Agora é aqui que podemos tirar proveito do nilad de altura da pilha. No momento em que começamos a pressionar 19, sabemos que já existem 2 itens na pilha, então []avaliaremos como 2. Podemos usar isso para criar 19 em menos bytes. A maneira óbvia é mudar o interior ()()()para ()[]. Isso economiza apenas dois bytes. Com um pouco mais de ajustes, podemos pressionar 19 com

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

Isso economiza 6 bytes. Agora estamos com 56.

Você pode ver essa dica sendo usada com muita eficácia nessas respostas:


Seu CATprograma realmente empurra TAC: P
Assistente de trigo

Além disso, 52 bytes
Wheat Wizard

2
Sei que é uma dica, mas não consigo me conter, 50 bytes .
Wheat Wizard

11
Outra dica estranha, mas às vezes eficaz, para ajudar a abusar []: preceder 0s com (<>)s antes do seu código. Isso realmente funciona se você planeja enviar o código de maneira inversa, mas se você encontrar o número certo, poderá reduzir bastante o código. Um pouco exagerado exemplo onde eu acrescentar 6 0s, o que me permite usar apenas como muitos []s como eu uso()
Jo rei

2

Use o wiki

Nós temos um wiki ! Tem algumas falhas, mas é um recurso útil. Ele possui listas de programas úteis e bem organizados que você pode colar no seu código. Se você quer fazer algo que acha que alguém poderia ter feito antes que haja uma boa chance de isso estar no wiki.


2

Melhor loop

Aqui está fácil:

Uma construção bastante comum é:

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

Onde você deseja fazer um loop n vezes, mas ainda assim manter o n. No entanto, isso pode ser escrito como:

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

para salvar 2 bytes.

Outro paradigma bastante comum é

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

O qual fará um loop e acumulará a pilha inteira. Devido a algumas matemáticas sofisticadas, é o mesmo que:

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

1

Verifique seus negativos

Às vezes, você pode jogar alguns bytes escolhendo estrategicamente o que negar com a [...]mônada.

Um exemplo simples está em [...]s aninhados . Por exemplo, [()()()[()()]]poderia ser apenas[()()()]()()

Digamos que você queira verificar se um valor é um dos colchetes iniciais (<{[. A tentativa inicial é empurrar a diferença entre cada caractere e o loop, subtraindo-o

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

No entanto, você pode economizar 4 bytes pressionando as versões negativas das diferenças!

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

Geralmente, isso não economiza muito, mas também custa muito pouco esforço para mudar o [...]ambiente. Esteja atento a situações em que você pode pressionar o negativo de um contador em vez do positivo para economizar no incremento várias vezes em vez de diminuir mais tarde. Ou trocando (a[b])com ([a]b)para fazer a diferença entre dois números negativos em vez de positivos.


11
Coisas semelhantes podem ser feitas com zeros <a<b>c>-> <abc>e <a[b]c>-> <abc>.
Wheat Wizard
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.