Алиса , 38 36 байт
Спасибо Лео за сохранение 2 байта.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
Попробуйте онлайн!
Почти наверняка не оптимально. Поток управления довольно сложный, и хотя я вполне доволен тем, сколько байтов было сохранено по сравнению с предыдущими версиями, у меня есть ощущение, что я слишком усложняю то, что может быть более простое и короткое решение.
объяснение
Во-первых, мне нужно немного разобраться со стеком обратных адресов Алисы (RAS). Как и во многих других фунгеоидах, у Алисы есть команда, позволяющая перемещаться по коду. Тем не менее, он также имеет команды, чтобы вернуться туда, откуда вы пришли, что позволяет довольно удобно реализовывать подпрограммы. Конечно, поскольку это двумерный язык, подпрограммы действительно существуют только по соглашению. Ничто не мешает вам войти в подпрограмму или выйти из нее с помощью других средств, кроме команды возврата (или в любой точке подпрограммы), и в зависимости от того, как вы используете RAS, в любом случае не может быть чистой иерархии перехода / возврата.
Как правило, это реализуется с помощью команды перехода, j
передающей текущий IP-адрес в RAS перед переходом. Команда return k
затем выводит адрес RAS и переходит туда. Если RAS пустой, k
ничего не делает вообще.
Есть и другие способы манипулирования РАН. Два из них имеют отношение к этой программе:
w
проталкивает текущий IP-адрес в RAS, никуда не переходя. Если вы повторите эту команду, вы можете написать простые циклы довольно удобно, как &w...k
я уже делал в предыдущих ответах.
J
похоже, j
но не запоминает текущий IP-адрес на RAS.
Также важно отметить, что RAS не хранит информацию о направлении IP. Поэтому возвращение к адресу с k
всегда сохранит текущее направление IP (и, следовательно, также, находимся ли мы в режиме кардинала или ординала) независимо от того, как мы прошли через j
илиw
иной адрес IP-адреса.
Разобравшись с этим, давайте начнем с рассмотрения подпрограммы в приведенной выше программе:
01dt,t&w.2,+k
Эта подпрограмма вытягивает нижний элемент стека n наверх, а затем вычисляет числа Фибоначчи F (n) и F (n + 1) (оставляя их сверху стека). Нам никогда не нужен F (n + 1) , но он будет отброшен вне подпрограммы из-за того, как &w...k
циклы взаимодействуют с RAS (какой тип требует, чтобы эти циклы были в конце подпрограммы). Причина, по которой мы берем элементы снизу, а не сверху, заключается в том, что это позволяет нам рассматривать стек больше как очередь, что означает, что мы можем вычислить все числа Фибоначчи за один раз, не сохраняя их в другом месте.
Вот как работает эта подпрограмма:
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.
Конец цикла немного сложнее. Пока в стеке есть копия адреса 'w', начинается следующая итерация. Как только они исчерпаны, результат зависит от того, как была вызвана подпрограмма. Если подпрограмма была вызвана с помощью 'j', последняя «k» возвращается туда, так что конец цикла удваивается как возврат подпрограммы. Если подпрограмма была вызвана с помощью 'J', и в стеке все еще есть адрес с более раннего адреса, мы переходим туда. Это означает, что если подпрограмма была вызвана в самом внешнем цикле, это «k» возвращает в начало этого внешнего цикла. Если подпрограмма была вызвана с 'J', но RAS сейчас пуст, тогда это 'k' ничего не делает, и IP просто продолжает двигаться после цикла. Мы будем использовать все три этих случая в программе.
Наконец, к самой программе.
/o....
\i@...
Это всего лишь два быстрых перехода в обычный режим для чтения и печати десятичных целых чисел.
После i
, есть a, w
который запоминает текущую позицию перед переходом в подпрограмму из-за второй /
. Этот первый вызов подпрограммы вычисляет F(n)
и F(n+1)
на входе n
. После этого мы возвращаемся назад, но сейчас движемся на восток, поэтому остальные операторы программы работают в режиме Cardinal. Основная программа выглядит так:
;B1dt&w;31J;d&+
^^^
Вот 31J
еще один вызов подпрограммы и, следовательно, вычисление числа Фибоначчи.
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.