Alice, 38 36 bytes
Thanks to Leo for saving 2 bytes.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
Try it online!
Almost certainly not optimal. The control flow is fairly elaborate and while I'm quite happy with how many bytes that saved over previous versions, I have a feeling that I'm overcomplicating things that there might be a simpler and shorter solution.
Explanation
First, I need to elaborate a bit on Alice's return address stack (RAS). Like many other fungeoids, Alice has a command to jump around in the code. However, it also has commands to return to where you came from, which lets you implement subroutines quite conveniently. Of course, this being a 2D language, subroutines really only exist by convention. There's nothing stopping you from entering or leaving a subroutine through other means than a return command (or at any point in the subroutine), and depending on how you use the RAS, there might not be a clean jump/return hierarchy anyway.
In general, this is implemented by having the jump command j
push the current IP address to the RAS before jumping. The return command k
then pops an address of the RAS and jumps there. If the RAS is empty, k
does nothing at all.
There are also other ways to manipulate the RAS. Two of these are relevant for this program:
w
pushes the current IP address to the RAS without jumping anywhere. If you repeat this command you can write simple loops quite conveniently as &w...k
, which I've already done in past answers.
J
is like j
but doesn't remember the current IP address on the RAS.
It's also important to note that the RAS stores no information about the direction of the IP. So returning to an address with k
will always preserve the current IP direction (and therefore also whether we're in Cardinal or Ordinal mode) regardless of how we passed through the j
or w
that pushed the IP address in the first place.
With that out the way, let's start by looking into the subroutine in the above program:
01dt,t&w.2,+k
This subroutine pulls the bottom element of the stack, n, to the top and then computes the Fibonacci numbers F(n) and F(n+1) (leaving them on top of the stack). We never need F(n+1), but it will be discarded outside the subroutine, due to how &w...k
loops interact with the RAS (which sort of requires these loops to be at the end of a subroutine). The reason we're taking elements from the bottom instead of the top is that this lets us treat the stack more like a queue, which means we can compute all the Fibonacci numbers in one go without having to store them elsewhere.
Here is how this subroutine works:
Stack
01 Push 0 and 1, to initialise Fibonacci sequence. [n ... 0 1]
dt, Pull bottom element n to top. [... 0 1 n]
t&w Run this loop n times... [... F(i-2) F(i-1)]
. Duplicate F(i-1). [... F(i-2) F(i-1) F(i-1)]
2, Pull up F(i-2). [... F(i-1) F(i-1) F(i-2)]
+ Add them together to get F(i). [... F(i-1) F(i)]
k End of loop.
The end of the loop is a bit tricky. As long as there's a copy of the 'w' address on the stack, this starts the next iteration. Once those are depleted, the result depends on how the subroutine was invoked. If the subroutine was called with 'j', the last 'k' returns there, so the loop end doubles as the subroutine's return. If the subroutine was called with 'J', and there's still an address from earlier on the stack, we jump there. This means if the subroutine was called in an outer loop itself, this 'k' returns to the beginning of that outer loop. If the subroutine was called with 'J' but the RAS is empty now, then this 'k' does nothing and the IP simply keeps moving after the loop. We'll use all three of these cases in the program.
Finally, on to the program itself.
/o....
\i@...
These are just two quick excursions into Ordinal mode to read and print decimal integers.
After the i
, there's a w
which remembers the current position before passing into the subroutine, due to the second /
. This first invocation of the subroutine computes F(n)
and F(n+1)
on the input n
. Afterwards we jump back here, but we're moving east now, so the remainder of the program operators in Cardinal mode. The main program looks like this:
;B1dt&w;31J;d&+
^^^
Here, 31J
is another call to the subroutine and therefore computes a Fibonacci number.
Stack
[F(n) F(n+1)]
; Discard F(n+1). [F(n)]
B Push all divisors of F(n). [d_1 d_2 ... d_p]
1 Push 1. This value is arbitrary. [d_1 d_2 ... d_p 1]
The reason we need it is due to
the fact that we don't want to run
any code after our nested loops, so
the upcoming outer loop over all
divisors will *start* with ';' to
discard F(d+1). But on the first
iteration we haven't called the
subroutine yet, so we need some
dummy value we can discard.
dt&w Run this loop once for each element [d_1 d_2 ... d_p 1]
in the stack. Note that this is once OR
more than we have divisors. But since [d_i d_(i+1) ... F(d_(i-1)) F(d_(i-1)+1)]
we're treating the stack as a queue,
the last iteration will process the
first divisor for a second time.
Luckily, the first divisor is always
1 and F(1) = 1, so it doesn't matter
how often we process this one.
; Discard the dummy value on the [d_1 d_2 ... d_p]
first iteration and F(d+1) of OR
the previous divisor on subsequent [d_i d_(i+1) ... F(d_(i-1))]
iterations.
31J Call the subroutine without pushing [d_(i+1) ... F(d_i) F(d_i+1)]
the current address on the RAS.
Thereby, this doubles as our outer
loop end. As long as there's an
address left from the 'w', the end
of the subroutine will jump there
and start another iteration for the
next divisor. Once that's done, the
'k' at the end of the subroutine will
simply do nothing and we'll continue
after it.
; Discard the final F(d_i+1).
d&+ Get the stack depth D and add the top [final result]
D+2 values. Of course that's two more
than we have divisors, but the stack is
implicitly padded with zeros, so that
doesn't matter.