Аліса , 38 36 байт
Дякуємо Лео за збереження 2 байтів.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
Спробуйте в Інтернеті!
Майже точно не оптимально. Контрольний потік досить складний, і хоча я цілком задоволений тим, скільки байтів збережено за попередні версії, у мене є відчуття, що я надмірно ускладнюю те, що може бути простіше і коротше рішення.
Пояснення
По-перше, мені потрібно трохи розробити стек зворотної адреси Аліси (RAS). Як і багато інших фрінгейдів, Аліса має команду стрибати навколо коду. Однак у нього також є команди повертатися туди, звідки ви прийшли, що дозволяє досить зручно реалізувати підпрограми. Звичайно, що це 2D мова, підпрограми дійсно існують лише за умовами. Ніщо не заважає вам входити або залишати підпрограму за допомогою іншого способу, ніж команда return (або в будь-якій точці підпрограми), і залежно від того, як ви використовуєте RAS, інакше не може бути ієрархії стрибків / повернень.
Взагалі це реалізується за рахунок того, щоб команда стрибок j
натискала поточну IP-адресу до RAS перед переходом. Потім команда return повертається k
до адреси RAS і стрибає туди. Якщо RAS порожній,k
взагалі нічого не робить.
Існують також інші способи маніпулювання RAS. Дві з них стосуються цієї програми:
w
натискає поточну IP-адресу до RAS, не перестрибуючи нікуди. Якщо ви повторите цю команду, ви можете написати прості цикли досить зручно &w...k
, що я вже робив у минулих відповідях.
J
як, j
але не пам'ятає поточну IP-адресу в RAS.
Важливо також зазначити, що RAS не зберігає інформації про напрямок ІС. Тож повернення до адреси з адресою 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
. Потім ми стрибаємо сюди, але зараз рухаємося на схід, тому решта програмних операторів у режимі Кардинала. Основна програма виглядає так:
;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.