Dicas para jogar golfe em brainfuck


23

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

Respostas:


25

Colocar uma dica por resposta seria muitas respostas.

  • Aprenda a pensar em Brainfuck. É muito diferente de qualquer outra coisa. Leia e escreva, reescreva e reescreva muitos programas de foda cerebral. Como o idioma não lhe dá muito trabalho, é importante usar o que ele oferece de maneira flexível e eficiente. Não deixe que abstrações se interponham entre você e a linguagem - entre e lute com ela.

  • Fique muito confortável com o controle de fluxo não destrutivo. Para sair de um loop de decisão, em vez de zerar a célula inicial copiando-a em outro lugar e depois copiando-a novamente após sair do loop, geralmente é melhor mover o ponteiro para um zero preexistente próximo. Sim, isso significa que o ponteiro estará em locais diferentes, dependendo de você ter passado pelo loop, mas isso também significa que esses locais provavelmente têm diferentes disposições de zeros e nonzeros próximos, que você pode usar para ressincronizar o local do ponteiro usando outro loop. Essa técnica é fundamental para uma boa programação do Brainfuck, e várias formas dela serão constantemente úteis.

  • Isso e o fato de que todos >ou os <custos significam que os detalhes do layout da memória são importantes. Experimente quantas variações de layout você tiver paciência. E lembre-se, seu layout de memória não precisa ser um mapeamento rígido de dados para locais. Pode se transformar ao longo da execução.

  • Em uma escala maior, considere e tente implementar uma variedade de algoritmos diferentes. Inicialmente, não será óbvio exatamente qual algoritmo será melhor; pode até não ser óbvio qual abordagem básica será melhor e provavelmente será algo diferente do que seria melhor em uma linguagem normal.

  • Se você estiver lidando com dados grandes ou de tamanho variável, verifique se há alguma maneira de lidar com eles localmente, sem ter que acompanhar o tamanho dele ou a sua localização numérica.

  • Os mesmos dados podem ser duas coisas diferentes. (Na maioria das vezes, um número ou caractere e também um marcador posicional diferente de zero. Mas veja random.b , onde um contador de bits funciona como o valor de uma célula de um autômato celular.)

  • O mesmo código pode fazer duas coisas diferentes, e é muito mais fácil fazê-lo em um idioma em que o código é tão genérico quanto <+<. Esteja atento a essas possibilidades. De fato, você pode ocasionalmente notar, mesmo no que parece ser um programa bem escrito, que existem pequenas porções que podem ser excluídas por completo, nada adicionado e, por acaso, a coisa ainda funciona perfeitamente.

  • Na maioria dos idiomas, você costuma usar um compilador ou intérprete para verificar o comportamento do seu programa. A linguagem Brainfuck exige maior controle conceitual; se você precisar de um compilador para lhe dizer o que o seu programa faz, não terá uma compreensão suficientemente firme do seu programa e provavelmente precisará encará-lo um pouco mais - pelo menos se desejar ter uma imagem clara o suficiente do programa. o halo conceitual de programas similares para ser bom no golfe. Com a prática, você estará produzindo uma dúzia de versões do seu programa antes de tentar executar uma e, nesse ponto, terá 95% de certeza de que a menor será executada corretamente.

  • Boa sorte! Pouquíssimas pessoas se preocupam em escrever Brainfuck de forma concisa, mas acho que é a única maneira pela qual a linguagem pode justificar a atenção contínua - como uma forma de arte incrivelmente obscura.


3
Lendo isso me faz querer tentar programar em Brainfuck agora ..
Claudiu

Puta merda, pensei ter reconhecido seu nome. Grande fã dos seus programas de cérebro, especialmente o seu superinterpretador super curto!
Jo rei

"você pode ocasionalmente perceber ... que existem pequenas porções que podem ser excluídas por completo, nada adicionado, e que, por acaso, ainda funcionaria perfeitamente". Isso é verdade, principalmente para iniciantes. Eu só escrevi uma resposta em BF até onde me lembro, mas seu histórico de revisões contém pelo menos três instâncias em que eu estava jogando com o programa e fui aleatoriamente "ei, o programa ainda funciona se eu remover esse bit! "
ETHproductions

"Experimente quantas variações de layout você desejar", ou pode
usar

9

Algumas dicas aqui:

Constantes:

A página de constantes dos Esolangs possui uma lista extremamente útil das maneiras mais curtas de criar valores específicos. Encontro-me consultando esta página pelo menos duas vezes por programa.

O começo de tudo:

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

Isso configura a fita no formato 3 * n ^ 2, que se parece com

3 6 12 24 48 96 192 128 0 0 '

Por que isso é tão importante?

Vamos descer a lista:

  • 3 e 6 são chatos
  • 12: Perto de 10 (nova linha) ou 13 (retorno de carro). Também pode ser usado no balcão para 0-9
  • 24: Perto de 26, o número de letras no alfabeto
  • 48: ASCII para 0
  • 96: Perto de 97, ASCII para a
  • 196 e 128: 196-128 = 64, próximo a 65, o ASCII para A.

A partir desse algoritmo, estamos no início de praticamente todas as seqüências no intervalo ASCII, juntamente com um contador para cada uma e uma nova linha de fácil alcance.

Um exemplo prático:

Imprimir todas as letras maiúsculas e minúsculas e dígitos.

Com algoritmo:

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

Sem:

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

Passamos a maior parte dos bytes apenas inicializando a fita no segundo exemplo. Parte disso é compensada pelos movimentos extras no primeiro exemplo, mas esse método tem claramente a vantagem.

Alguns outros algoritmos interessantes na mesma linha:

3 * 2 ^ n + 1:

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

Isso compensa os valores em 1, o que realiza algumas coisas. Torna os 12 um retorno de carro, os 64 o início real do alfabeto maiúsculo e os 24 mais próximos de 26.

2 ^ n:

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

Como 64 é bom para letras maiúsculas, 32 é o ASCII para espaço e 128 pode ser usado como contador de 26 (130/5 = 26). Isso pode salvar bytes em certas situações em que não são necessários dígitos e letras minúsculas.

Escolha a implementação que se adequa à pergunta:

  • As células negativas são quase sempre úteis e não há motivos para evitá-las (a menos que isso não mude sua contagem de bytes)
  • Quase exatamente a mesma coisa com células de quebra automática, ainda mais porque muitas constantes usam quebra automática.
  • Os tamanhos de células arbitrárias são úteis para sequências matemáticas infinitas, como o cálculo infinito da sequência de Fibonacci ( +[[-<+>>+>+<<]>]) ou o processamento de números maiores / negativos. A desvantagem é que alguns métodos comuns, como [-]e [->+<]não podem ser invocados, apenas no caso de o número ser negativo.
  • EOF como 0, -1 ou nenhuma alteração. 0 é geralmente preferível, pois você pode repetir uma entrada inteira sem verificações extras. -1 é útil ao fazer um loop sobre estruturas de matriz. Ainda não encontrei um uso sem alterações :(.

Acompanhe o que está acontecendo:

Em todos os momentos, você deve ter comentários sobre onde o ponteiro deve estar em relação aos dados ao seu redor e certifique-se de conhecer o intervalo de valores possíveis de cada célula. Isso é especialmente importante quando você divide o ponteiro antes de um loop, porque você deseja unir as duas possibilidades depois.

A qualquer momento, meu código está repleto de comentários em todas as outras linhas que se parecem com isso:

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

Alguns conselhos extras são para atribuir significados especiais aos símbolos. No exemplo acima, 'é onde está o ponteiro, *significa repetição nessa direção, ?significa uma célula com valor desconhecido, !0significa uma célula diferente de zero, _é um substituto -e pé um substituto para +. orimplica que a fita possa se parecer com qualquer uma das representações e precisa ser manuseada como tal.

Seu esquema de símbolos não precisa necessariamente ser o mesmo que o meu (que tem algumas falhas), apenas precisa ser consistente. Isso também é extremamente útil ao depurar, porque você pode executá-lo até esse ponto e comparar a fita real com a que você deve ter, o que pode indicar possíveis falhas no seu código.


5

Meu conselho principal seria não.

OK, tudo bem, você quer algo mais útil que isso. O AM já é uma linguagem muito concisa, mas o que realmente o mata é a aritmética, que efetivamente precisa ser feita de forma unária. Vale a pena ler a página de constantes em Esolang para escolher exatamente como escrever números grandes com eficiência e explorar o agrupamento sempre que possível.

O acesso à memória também é muito caro. Como você está lendo uma fita, lembre-se de onde sua cabeça está se movendo a qualquer momento. Ao contrário de outras línguas, onde você pode apenas escrever a, b, c, na bf você tem que mover-se explicitamente a cabeça um determinado número de bytes para a esquerda ou direita, por isso você deve estar consciente de onde você armazena o quê. Tenho certeza de que organizar sua memória da maneira ideal é difícil para o NP, então boa sorte com isso.


5

Nesta resposta, vou me referir a uma célula específica na fita várias vezes. Não importa qual célula é, mas é a mesma célula em toda a resposta. Para os fins deste post, chamarei essa célula de "Todd".

Ao tentar definir uma célula para um valor constante, às vezes vale a pena não terminá-la imediatamente. Por exemplo, digamos que você queira que Todd contenha 30. Posteriormente, em seu código (que pode modificar o valor de Todd, mas nunca o lê), você volta a Todd. Se o valor de Todd for 0, o programa será encerrado. Caso contrário, o valor de Todd será impresso para sempre.

De acordo com a página esolangs.org de constantes que envolvem o cérebro (que provavelmente pode ser o assunto de uma dica por conta própria!), A maneira mais curta de obter 30 é >+[--[<]>>+<-]>+. Essa liderança >está lá apenas para garantir que nada à esquerda do ponteiro seja modificado, mas, neste caso, presumiremos que não nos importamos com isso e descartá-lo. Usando esse código, seu código seria algo como isto:

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

Você pode pensar no primeiro pedaço de código assim:

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

Mas lembre-se os dois últimos caracteres em que pedaço: >+. É igualmente válido pensar desta maneira:

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

Observe que você (GO TO TODD)duas vezes! Você poderia escrever seu código desta maneira:

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

Supondo que o número de bytes necessários (GO TO TODD)seja o mesmo antes, um movimento a menos == um a menos byte! Às vezes, o fato de sua posição inicial ter mudado tira esse benefício, mas nem sempre.


0

Uma pequena dica para desafios sem contribuição. Você pode usar, em ,vez de [-], se precisar limpar a célula rapidamente, pois a maioria dos intérpretes (incluindo o TIO.run) definirá o conteúdo da célula como representação EOF como zero. Isso torna os programas um pouco pouco portáveis, mas quem se importa com isso no golfe de código?

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.