Conseils pour jouer au golf à Brain-Flak

24

Brain-flak est un langage basé sur la pile de turing-tarpit, écrit en collaboration entre moi, DJMcMayhem et 1000000000 .

Certains utilisateurs sont très expérimentés dans les voies mystérieuses de Brain-Flak. J'ai donc pensé que c'était une bonne idée de poser cette question comme un moyen pour nous, et j'espère que d'autres aussi, de partager nos connaissances avec la communauté et d'abaisser la barre d'entrée dans cette langue "conçue pour être pénible à utiliser". Et peut-être même enseigner à ceux d'entre nous qui ont plus d'expérience de nouveaux trucs.

Alors, quels conseils avez-vous pour jouer au golf en brain-flak? Je cherche des idées qui peuvent être appliquées aux problèmes de golf de code en général qui sont au moins quelque peu spécifiques au brain-flak (par exemple, "supprimer les commentaires" n'est pas une réponse).

Veuillez poster un pourboire par réponse.

Assistant de blé
la source

Réponses:

22

Utilisez la troisième pile

Si vous avez lu le titre, vous pourriez être un peu confus. Il n'y a sûrement que deux piles dans Brain-Flak? Cependant, je vous assure qu'il existe et qu'il est l'un des outils les plus puissants sinon le plus puissant pour écrire et jouer au Brain-Flak.


Qu'est-ce que la "troisième pile"?

Chaque programme Brain-Flak utilise la troisième pile d'une manière ou d'une autre, mais la majeure partie de l'utilisation se déroule en arrière-plan et il est souvent utile d'ignorer simplement le fait qu'elle existe. Chaque parenthèse du programme ajoute ou supprime un élément de la pile. Trois des accolades ouvertes ([<ajoutent toutes un élément à la pile tandis que leurs trois conjugués )]>retirent tous un élément de la pile. La valeur de l'élément sur la pile est la valeur de la portée actuelle du programme et l'utilisation de nilads modifiera cette valeur de certaines manières. La parenthèse fermée )a la fonction unique de déplacer un élément de la troisième pile vers la pile actuelle; une poussée.

J'espère que cela devient clair pour vous. La troisième pile est une sorte de pile qui se souvient des valeurs de retour du code qui a déjà été exécuté. Passons en revue un exemple de programme simple gardant une trace des deux piles normales et de la troisième pile.

Exemple

Nous allons parcourir le programme suivant. Ce programme pousse -3, 1, -2vers la pile.

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

Nous commençons avec trois accolades ouvertes, qui poussent toutes un zéro vers la troisième pile.

Nos piles ressemblent maintenant à ceci, la troisième pile est celle de droite et la pile active a un ^dessous:

        0
        0
  0  0  0
  ^

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

Maintenant, nous avons trois ()nilades. Celles-ci ne font rien aux deux piles normales, mais elles en ajoutent chacune une en haut de la troisième pile, ce qui donne à nos piles l'apparence suivante:

        3
        0
  0  0  0
  ^

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

Maintenant, nous rencontrons un ]comme indiqué avant les accolades proches retirent un élément de la troisième pile, mais ]a la fonction de soustraire l'élément qu'il supprime du haut de la pile. Ainsi, nos nouvelles piles ressembleront à:

       -3
  0  0  0
  ^

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

C'est logique; [...]fait la négation ]devrait donc soustraire vers le bas.

Nous devons maintenant exécuter a ). Comme vous vous en souvenez probablement, )c'est l'endroit dans le programme où les choses sont poussées vers la pile, donc nous déplacerons le haut de la troisième pile vers la pile actuelle, en plus nous ajouterons l' -3élément suivant à l'élément dans la troisième pile.

 -3  0 -3
  ^

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

Une fois de plus, nous rencontrons l'un de nos trois accolades ouvertes, nous allons donc ajouter un autre élément à notre troisième pile.

        0
 -3  0 -3
  ^

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

Comme nous l'avons dit plus tôt, ()nous incrémenterons le haut de notre troisième pile d'une unité.

        1
 -3  0 -3
  ^

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

Et )déplacera le haut de la troisième pile sur la pile active et ajoutera vers le bas

  1
 -3  0 -2
  ^

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

Le dernier )déplace la troisième pile sur la pile active et comme il n'y a plus d'éléments à ajouter sur la troisième pile, ne fait rien d'autre.

 -2
  1
 -3  0
  ^

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

Le programme est terminé donc nous terminons et sortons.


Cet exemple est destiné à vous donner une idée de ce qu'est et fait la troisième pile. Il n'inclut pas toutes les opérations, mais j'espère que vous pourrez comprendre ce que chacune fait d'elle-même. Si vous avez encore du mal, j'ai inclus une "feuille de triche" au bas de cette réponse pour vous aider.

Okay, alors quoi?

Ok, maintenant vous comprenez la troisième pile, mais "Et alors"? Vous l'utilisiez déjà, même si vous ne l'appeliez pas "Third Stack", comment la pensée en termes de Third Stack vous aide-t-elle à jouer au golf?

Regardons un problème. Vous voulez prendre le triangle d'un nombre . Il s'agit de la somme de tous les nombres inférieurs à n.

Une approche pourrait consister à créer un accumulateur sur la pile et à y ajouter au fur et à mesure que vous comptez à rebours. Cela crée du code qui ressemble à ceci:

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

Essayez-le en ligne!

Ce code est assez compact et on pourrait penser qu'il ne peut pas devenir beaucoup plus petit. Cependant, si nous l'abordons d'un troisième point de vue de la pile, il devient clair que cela est extrêmement inefficace. Au lieu de mettre notre accumulateur sur la offstack, nous pouvons le mettre sur la troisième pile avec un (et le récupérer à la fin que nous utilisons ). Nous allons à nouveau parcourir tous les chiffres, mais cette fois, nous n'avons pas grand-chose à faire pour augmenter notre troisième pile, le programme le fait pour nous. Cela ressemble à ceci:

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

Essayez-le en ligne

Ce code est moins de la moitié de la taille de la version assez bien golfée que nous avons faite auparavant. En fait, une recherche informatique a prouvé que ce programme est le programme le plus court possible pouvant effectuer cette tâche. Ce programme peut être expliqué en utilisant l'approche "somme de tous les runs", mais je pense qu'il est beaucoup plus intuitif et clair lorsqu'il est expliqué en utilisant une approche Third Stack.

Quand dois-je utiliser la troisième pile?

Idéalement, chaque fois que vous commencez à travailler sur un nouveau problème dans Brain-Flak, vous devriez penser à vous-même comment pourrais-je faire cela avec la troisième pile à l'esprit. Cependant, en règle générale, chaque fois que vous devez suivre un certain type d'accumulateur ou avoir un total cumulé, c'est une bonne idée d'essayer de le mettre sur votre troisième pile au lieu des deux vraies piles.

Une autre fois où cela peut être une bonne idée d'envisager d'utiliser votre troisième pile, c'est quand vous n'avez pas d'espace pour stocker de la valeur sur les deux autres piles. Cela peut être particulièrement utile lorsque vous effectuez des manipulations sur deux piles existantes et que vous souhaitez enregistrer une valeur pour une utilisation ultérieure sans avoir à suivre où elle se trouve.

Limitations de la troisième pile

La troisième pile est très puissante à bien des égards, mais elle a ses propres limites et inconvénients.

Premièrement, la hauteur de pile maximale pour la troisième pile à tout moment donné est déterminée au moment de la compilation. Cela signifie que si vous souhaitez utiliser une quantité d'espace sur la pile, vous devez allouer cet espace lorsque vous écrivez le programme.

Deuxièmement, la troisième pile n'est pas un accès aléatoire. Cela signifie que vous ne pouvez effectuer aucune opération sur une valeur autre que la valeur la plus élevée. De plus, vous ne pouvez pas déplacer les valeurs sur la pile (disons permuter les deux premiers éléments).

Conclusion

La troisième pile est un outil puissant et je considère qu'elle est essentielle pour chaque utilisateur de Brain-Flak. Il faut un certain temps pour s'y habituer et nécessite un changement dans votre façon de penser à la programmation dans Brain-Flak, mais lorsqu'il est utilisé correctement, il fait toute la différence entre un décent et un incroyable en matière de golf.

Cheatsheet

Voici une liste des opérations et comment elles affectent la troisième pile

 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
Assistant de blé
la source
1
Wow, astuce fantastique! En fait, je venais juste ici pour écrire une réponse similaire quand j'ai vu cela. +1! Je préfère le considérer comme l'accumulateur , mais la métaphore de la troisième pile est vraiment intéressante.
DJMcMayhem
Je l'ai toujours appelé "espace de travail" ou "pile de travail".
sagiksp
Sur la version TIO de Brainflak, {...}retourne la somme de toutes les itérations.
CalculatorFeline
@CalculatorFeline Oui, cela est vrai sur presque toutes les versions de Brain-Flak, à l'exception de certaines très anciennes. Cependant, je ne sais pas pourquoi vous avez commenté cela sur ce post en particulier.
Wheat Wizard
<>,{,} | Nothing
CalculatorFeline
21

Trouver le module / reste

Trouver n modulo m est l'une des opérations arithmétiques de base, importante pour de nombreux défis. Pour les cas m> 0 et n> = 0 , l'extrait de code de 46 octets suivant peut être utilisé. Il suppose que n est au-dessus de la pile active avec m la suivante vers le bas et les remplace par n mod m . Il laisse le reste des piles intactes.

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

Cette version annotée montre le contenu de la pile à certains points du programme. ;sépare les deux piles et .marque la pile active.

. n m;
({}(<>))<>
{   . m; r 0   (r = n - km)
    (({}))
    . m m; r 0
    {({}[()])<>}
    {}
}
m-n%m-1 m; . 0
{}<>([{}()]{})
. n%m;
feersum
la source
Il m'a fallu un certain temps pour comprendre ce que la partie non annotée a fait ( {({}[()])<>}), mais une fois que j'ai compris ... Genius :-)
ETHproductions
11

Redondance Push-Pop

C'est un gros problème. C'est aussi un peu nuancé.

L'idée est que si vous poussez quelque chose et que vous le faites éclater sans rien faire, vous n'auriez pas dû le pousser du tout.

Par exemple, si vous souhaitez déplacer quelque chose vers la pile secondaire puis en ajouter un, vous pouvez écrire le code suivant:

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

Cela peut être plus simple comme ceci:

({}<>())

Le premier programme ramasse l'article, le déplace, le reprend et en ajoute un, tandis que le second fait les deux d'un seul coup.

Cet exemple était simple mais il peut devenir beaucoup plus complexe. Prenez par exemple:

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

La réduction est moins claire ici mais la 4ème pop du programme peut être réduite avec la 2ème pression, comme ceci:

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

C'est l'outil le plus puissant de mon répertoire de golf, mais il faut un peu de pratique pour s'y perfectionner. Une fois que vous les avez effectuées pendant un certain temps, vous pourrez les repérer presque instantanément.

Assistant de blé
la source
Dans ce dernier exemple, la partie de la première paire de parenthèses n'est-elle pas équivalente à ({}<{}>)?
feersum
@feersum Non, ce n'est pas le cas. Il déplace une copie du deuxième élément de la pile vers la pile hors tout en ({}<{}>)détruisant entièrement l'élément. Cependant, ces programmes n'étaient pas vraiment censés être optimaux simplement pour souligner le principe à l'œuvre ici.
Wheat Wizard
6

Optimisez vos entiers

Les entiers sont fastidieux à représenter dans Brain-Flak. Heureusement, nous avons une question qui aidera Golf a Brain-Flak Integer pour vous. (Notez que la question est conçue pour pousser l'entier vers la pile, donc la redondance push-pop s'applique probablement aux programmes plus réalistes.)

Neil
la source
Notez que nous avons également brain-flak.github.io/integer/ , qui exécute l'un de ces algorithmes en ligne pour plus de commodité.
DJMcMayhem
@DrMcMoylex Tout ce dont nous avons besoin maintenant comme dans l'implémentation du métagolfer entier dans Brain-Flak lui-même!
Neil
1
Nous l'avons. codegolf.stackexchange.com/a/98054/31716 : D
DJMcMayhem
5

Compteurs de boucles supplémentaires Push

Souvent, vous voudrez faire quelque chose comme

Effectuer une opération X sur chaque élément de la pile

ou

Effectuer une opération X sur chaque paire d'éléments adjacents de la pile

Lorsque l'entrée peut contenir des «0» ou que le résultat de l'opération X peut donner un 0, cela n'est vraiment pas pratique. Parce que vous devrez faire:

([])
{

  {}

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

}

Pour faire X à chaque élément, puis plus tard

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

Pour inverser à nouveau le tableau.

Pire encore, si nous opérons sur des paires d'éléments adjacents, nous devrons faire ([][()])à la place de ([]). C'est vraiment gênant. Voici l'astuce: pendant que vous faites X pour chaque élément, poussez un 1 sur la pile alternative juste au-dessus de X(element). Ensuite, pendant que vous l'inversez, vous pouvez simplement faire

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

Ceci est 8 octets plus court, donc lorsque vous prenez en compte le code supplémentaire pour pousser 1, vous finirez par économiser 4 à 6 octets. Pour un exemple plus concret, comparez la tâche d'obtention des deltas d'un tableau. Sans cette astuce, vous auriez besoin de:

([][()]){

    {}

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

    ([][()])

}{}{}<>

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

Pour 62. Avec cette astuce, vous aurez

([][()]){

    {}

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

    ([][()])

}{}{}<>

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

Pour 58. Si utilisé de la bonne façon (par exemple, inverser d'abord et supprimer deux ([][()])de plus tard), cela pourrait économiser encore plus dans des scénarios spécifiques.

DJMcMayhem
la source
3

Profitez de la nilade 'Stack-Height'

Surtout dans les , ou les défis où vous connaissez toujours la taille de l'entrée, vous pouvez profiter de la nilade 'Stack-Height' []pour créer des entiers.

Travaillons à travers cela avec un défi hypothétique: la sortie CAT. La manière non golfique est d'utiliser le golfeur entier en ligne pour pousser 67, 65 et 84. Cela donne:

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

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

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

(Newlines pour plus de clarté). Cela fait 88 octets, et ce n'est pas terrible. Si nous poussons plutôt les différences consécutives entre les valeurs, nous pouvons économiser beaucoup. Nous encapsulons donc le premier numéro dans un appel push , et soustrayons 2:

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

Ensuite, nous prenons ce code, l'enveloppons dans un appel push et ajoutons 19 à la fin:

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

C'est 62 octets, pour un golf de 26 octets!

Maintenant ici est là que nous arrivons à tirer parti de la pile nilad hauteur. Au moment où nous commençons à pousser 19, nous savons qu'il y a déjà 2 éléments sur la pile, donc []nous évaluerons 2. Nous pouvons l'utiliser pour créer un 19 en moins d'octets. La manière évidente est de changer l'intérieur ()()()en ()[]. Cela n'économise cependant que deux octets. Avec un peu plus de bricolage, il s'avère que nous pouvons pousser 19 avec

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

Cela nous fait gagner 6 octets. Nous en sommes maintenant à 56.

Vous pouvez voir que cette astuce est utilisée très efficacement sur ces réponses:

DJMcMayhem
la source
Votre CATprogramme pousse en fait TAC: P
Wheat Wizard
Aussi, 52 octets
Wheat Wizard
2
Je sais que c'est une astuce mais je ne peux pas m'en empêcher, 50 octets .
Wheat Wizard
1
Autre astuce étrange mais parfois efficace pour []éviter les abus : ajouter des 0s à des (<>)s avant votre code. Cela ne fonctionne vraiment que si vous prévoyez de pousser le code en sens inverse de toute façon, mais si vous trouvez le bon nombre, vous pouvez réduire votre code un peu. Un exemple assez extrême où j'ajoute 6 0s, ce qui me permet d'utiliser autant de []s que j'utilise()
Jo King
2

Utilisez le wiki

Nous avons un wiki ! Il a quelques brèves lacunes, mais c'est une ressource utile. Il contient des listes de programmes utiles et bien conçus pour la pile que vous pouvez coller dans votre code. Si vous voulez faire quelque chose que vous pensez que quelqu'un aurait pu faire avant, il y a de fortes chances que ce soit sur le wiki.

Assistant de blé
la source
2

Meilleur bouclage

En voici une simple:

Une construction assez courante est:

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

Où vous voulez boucler n fois mais gardez toujours le n. Cependant, cela peut s'écrire:

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

pour économiser 2 octets.

Un autre paradigme assez courant est

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

Qui bouclera et accumulera la pile entière. En raison de quelques calculs fantaisistes, c'est la même chose que:

(([]){[{}]...([])}{})
Assistant de blé
la source
1

Vérifiez vos négatifs

Parfois, vous pouvez jouer au golf sur quelques octets en choisissant stratégiquement ce qui doit être annulé avec la [...]monade.

Un exemple simple se trouve dans les [...]s imbriqués . Par exemple, [()()()[()()]]pourrait simplement être[()()()]()()

Supposons que vous vouliez vérifier si une valeur est l'une des parenthèses de début (<{[. La tentative initiale est de pousser la différence entre chaque caractère et de le boucler en le soustrayant

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

Cependant, vous pouvez économiser 4 octets en poussant les versions négatives des différences à la place!

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

Généralement, cela ne vous fait pas beaucoup économiser, mais cela coûte aussi très peu d'efforts pour changer ce qui l' [...]entoure. Soyez à l'affût des situations où vous pouvez pousser le négatif d'un compteur au lieu du positif pour économiser sur l'incrémentation plusieurs fois au lieu de la décrémenter plus tard. Ou échanger (a[b])avec ([a]b)pour faire la différence entre deux nombres négatifs au lieu de positifs.

Jo King
la source
1
Des choses similaires peuvent être faites avec des zéros <a<b>c>-> <abc>et <a[b]c>-> <abc>.
Wheat Wizard