Use the Third Stack
If you have read the title you might be a bit confused. Surely there are only two stacks in Brain-Flak? However I assure you that it exists and it is one of the most powerful if not the most powerful tool in writing and golfing Brain-Flak.
What is the "Third Stack"?
Every Brain-Flak program uses the third stack in one way or another but most of the use goes on behind the scenes and it is often useful to simply ignore the fact that it exists. Each parenthesis in the program either adds or removes one item from the stack. Three of the open braces ([<
all add an item to the stack while their three conjugates )]>
all remove an item from the stack. The value of the item on the stack is the value of the current scope of the program and using nilads will modify this value in certain ways. The close parenthesis )
has the unique function of moving an element from the Third Stack to the current stack; a push.
Hopefully this is becoming clear to you. The Third Stack is some sort of stack that remembers the return values of code that has already been executed. Let's walk through an example of a simple program keeping track of the two normal stacks and the Third Stack.
Example
We will walk through the following program. This program pushes -3, 1, -2
to the stack.
(([()()()])(()))
We start with three open braces, which all push a zero to the third stack.
Our stacks now look like this, the Third Stack is the one on the right and the active stack has a ^
under it:
0
0
0 0 0
^
(([()()()])(()))
^
Now we have three ()
nilads. These do not do anything to the normal two stacks, however they do each add one to the top of the Third Stack making our stacks look like:
3
0
0 0 0
^
(([()()()])(()))
^
Now we encounter a ]
as stated before close braces remove an item from the Third Stack, but ]
has the function of subtracting the element it removes from the top of the stack. Thus our new stacks will look like:
-3
0 0 0
^
(([()()()])(()))
^
This makes sense; [...]
does negation so ]
should subtract downwards.
Now we must execute a )
. As you likely recall )
is the place in the program where stuff gets pushed to the stack so we will be moving the top of the Third Stack to the current stack, in addition we will be adding the -3
to the next element in the third stack.
-3 0 -3
^
(([()()()])(()))
^
Once again we encounter one of our three open braces so we will add another element to our Third Stack.
0
-3 0 -3
^
(([()()()])(()))
^
As we said earlier ()
will increment the top of our third stack by one.
1
-3 0 -3
^
(([()()()])(()))
^
And )
will move the top of the Third Stack onto the active stack and add downwards
1
-3 0 -2
^
(([()()()])(()))
^
The last )
moves the Third Stack onto the active stack and since there are no elements left on the Third Stack for it to add, does nothing else.
-2
1
-3 0
^
(([()()()])(()))
^
The program is over so we terminate and output.
This example is intended to give you a feel for what the Third Stack is and does. It does not include all of the operations, but hopefully you can figure out what each of them does on its own. If you are still struggling I have included a "cheatsheet" at the bottom of this answer to help you along.
Ok, So what?
Ok, now you understand the Third Stack, but "So what"? You were already using it even if you didn't call it the "Third Stack", how does thinking in terms of the Third Stack help you golf?
Lets look at a problem. You want to take the Triangle of a number. This is the sum of all numbers less than n.
One approach might be to create an accumulator on the offstack and add to it as you count down. This creates code that looks like this:
(<>)<>{(({}[()])()<>{})<>}{}<>({}<>)
Try it Online!
This code is pretty compact and one might think that it can't get much smaller. However if we approach it from a third stack point of view it become clear that this is grossly inefficient. Instead of putting our accumulator on the offstack we can put it on the third stack with a (
and retrieve it at the end we use )
. We will once again loop through all the numbers, but this time we don't have to do much of anything to increment our Third Stack, the program does it for us. This looks like:
({()({}[()])}{})
Try it Online
This code is less than half the size of the pretty well golfed version we made before. In fact a computer search has proven that this program is the shortest possible program that can perform this task. This program can be explained using the "sum of all runs" approach, but I think it is a lot more intuitive and clear when explained using a Third Stack approach.
When do I use the Third Stack?
Ideally whenever you begin work on a new problem in Brain-Flak you should think to yourself how would I do this with the Third Stack in mind. However as a general rule of thumb whenever you have to keep track of some type of accumulator or have a running total it is a good idea to try putting that on your third stack instead of the two real stacks.
Another time that it might be a good idea to consider using your third stack is when you don't have any space to store some value on the other two stacks. This can be particularly useful when you are doing manipulations on two existing stacks and you want to save a value for later use without having to keep track of where it is.
Limitations of the Third Stack
The Third Stack is very powerful in a lot of ways but it comes with its own limitations and drawbacks.
Firstly, the maximum stack height for the Third Stack at any given point is determined at compile time. This means that if you want to use an amount of space on the stack you have to allocate that space when you are writing the program.
Secondly the Third Stack is not Random Access. This means that you cannot perform any operations on any value but the top most value. In addition you cannot move values around on the stack (say swap the first two elements).
Conclusion
The Third Stack is a powerful tool and I would consider it essential to every Brain-Flak user. It takes some getting used to and requires a shift in the way your think about programming in Brain-Flak, but when used properly it makes all the difference between a decent and an amazing when it comes to golfing.
Cheatsheet
Here is a list of operations and how they affect the Third Stack
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
1Wow, fantastic tip! I was actually just coming here to write a similar answer when I saw this. +1! I prefer to think of it as The Accumulator, but The Third Stack metaphor is really interesting. – James – 2017-02-17T18:34:55.377
I always called it the "workspace" or "workstack". – sagiksp – 2017-03-30T05:09:11.530
On the TIO version of Brainflak,
{...}
returns the sum of all iterations. – CalculatorFeline – 2017-06-04T21:50:32.730@CalculatorFeline Yes, this is true on nearly all versions of Brain-Flak except for some very early ones. However I'm not sure why you have commented that on this post in particular. – Post Rock Garf Hunter – 2017-06-04T23:10:26.513
<>,{,} | Nothing
– CalculatorFeline – 2017-06-04T23:10:49.487@CalculatorFeline Ah, I see. What I said is correct,
– Post Rock Garf Hunter – 2017-06-04T23:22:26.767{...}
does do nothing. Since{
does not create a new scope on the third stack all of its contents are added to any earlier scope. I could explain this better if I didn't have the character restriction, If you would like to talk further we can move over to the third stack.Also, the Third Stack interpretation lowers the requirement of everything matching to only
{}
matching. Also,(<[
are all the same, except for close bracket type. – CalculatorFeline – 2017-06-05T04:31:25.340@DJMcMayhem Accumulator usually implies one integer though... – Erik the Outgolfer – 2017-06-23T08:47:13.967