-10 bytes thanks to @Nitrodon!
{(({})[()])}{}((())){({(()[{}]({}))([{}]({}))}({}{})[{}])(({}({}))[({}[{}])])}{}
Try it online!
We already have another Brain-Flak answer here, but it uses both stacks and so doesn't work in Miniflak (a restricted subset of Brain-Flak where <
, >
and []
are disallowed). To avoid the second stack, this program uses a different multiplication algorithm.
Explanation
{(({})[()])}
{ } # While the top of the stack is nonzero:
{} # Pop the stack
( ) # Push a copy on the stack
[()] # Subtract 1
( ) # Push the result
This part counts down from the input value to 0, leaving a copy of each number in order.
{}((()))
{} # Pop the zero on the top
((())) # Push 1 twice
These extra ones are there so that when the input is 0 or 1, we multiply them together to get 1 instead of accidentally multiplying something by 0.
{({(()[{}]({}))([{}]({}))}({}{})[{}])(({}({}))[({}[{}])])}{}
{ } # While the top of the stack is nonzero:
({(()[{}]({}))([{}]({}))}({}{})[{}]) # Multiply the top two values
(({}({}))[({}[{}])]) # Swap the top two values
{} # Remove the zero on top
This loop is the core of the program: at each step, it multiplies the top two numbers together and then brings the number below them to the top. When we run out of numbers, a zero gets swapped to the top, and the loop ends. We then remove that zero, and the result of multiplying all the numbers together (which is the factorial of the input, as the numbers counted down from it to 1) is left.
How does this multiplication algorithm work?
(Suppose the top two numbers on the stack are a
and b
.)
({(()[{}]({}))([{}]({}))}({}{})[{}])
{} # Pop a
[ ] # Subtract it ...
() # ... from 1
({}) # Add b
( ) # Push the result
[{}] # Subtract that ...
({}) # ... from b ...
( ) # and push the result
{ } # Repeat until a reaches 0, keeping a running total of the sum of both results
{}{} # Pop a and b, add them together, ...
( )[{}] # ... and ignore the result
( ) # Push the running total
During each run-through, a
(the top of the stack) is replaced by b-(b+(1-a))
, which equals a-1
. This repeats until a
reaches 0, so the number of iterations is equal to the first input. The running total keeps track of the sum of the two results at each iteration. The first result is b+(1-a)
and the second is a-1
, so their sum is always b
, the second input. This means that keeping track of the running total yields the product of the two inputs. Finally, before pushing the product, we pop a
and b
because we don't need them anymore.
The last piece is the swapping algorithm:
(({}({}))[({}[{}])])
{} # Pop the top number
({}) # Add the second number
( ) # Push the result
{} # Pop the sum
{} # Pop the second number ...
[ ] # ... and subtract it from the sum
( ) # Push the result (the first number) ...
[ ] # ... and subtract that from the previous result (the sum)
( ) # Push the final result (the second number)
Since the first number is pushed back before the second one, their order is swapped from before.