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, -2
vers 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
{...}
retourne la somme de toutes les itérations.<>,{,} | Nothing
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.la source
{({}[()])<>}
), mais une fois que j'ai compris ... Genius :-)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.
la source
({}<{}>)
?({}<{}>)
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.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.)
la source
Compteurs de boucles supplémentaires Push
Souvent, vous voudrez faire quelque chose comme
ou
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 deX(element)
. Ensuite, pendant que vous l'inversez, vous pouvez simplement faireCeci 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.la source
Profitez de la nilade 'Stack-Height'
Surtout dans les défis de complexité kolmogorov , 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 évaluerons2
. 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 avecCela 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:
Hello World V1
Hello World V2
Magic The Gathering, ami ou ennemi
Sortie de l'entier manquant
Alphabet diagonal (mon préféré)
la source
CAT
programme pousse en faitTAC
: P[]
éviter les abus : ajouter des0
s à 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 60
s, ce qui me permet d'utiliser autant de[]
s que j'utilise()
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.
la source
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:
la source
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 soustrayantCependant, vous pouvez économiser 4 octets en poussant les versions négatives des différences à la place!
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.la source
<a<b>c>
-><abc>
et<a[b]c>
-><abc>
.