Alice , 38 36 byte
Cảm ơn Leo đã lưu 2 byte.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
Hãy thử trực tuyến!
Hầu như chắc chắn không tối ưu. Luồng điều khiển khá phức tạp và trong khi tôi khá hài lòng với số lượng byte được lưu so với các phiên bản trước đó, tôi có cảm giác rằng tôi đang quá lạm dụng những thứ có thể có một giải pháp đơn giản và ngắn hơn.
Giải trình
Đầu tiên, tôi cần xây dựng một chút về ngăn xếp địa chỉ trả lại của Alice (RAS). Giống như nhiều loại nấm khác, Alice có lệnh nhảy xung quanh trong mã. Tuy nhiên, nó cũng có các lệnh để trở về nơi bạn đến, cho phép bạn triển khai chương trình con khá thuận tiện. Tất nhiên, đây là ngôn ngữ 2D, chương trình con thực sự chỉ tồn tại theo quy ước. Không có gì ngăn bạn vào hoặc rời khỏi chương trình con thông qua các phương tiện khác ngoài lệnh trả về (hoặc tại bất kỳ điểm nào trong chương trình con), và tùy thuộc vào cách bạn sử dụng RAS, dù sao đi nữa, có thể không có phân cấp nhảy / trả lại sạch.
Nói chung, điều này được thực hiện bằng cách có lệnh nhảy j
đẩy địa chỉ IP hiện tại đến RAS trước khi nhảy. Lệnh return k
sau đó bật một địa chỉ của RAS và nhảy tới đó. Nếu RAS trống, k
không làm gì cả.
Cũng có những cách khác để thao túng RAS. Hai trong số này có liên quan đến chương trình này:
w
đẩy địa chỉ IP hiện tại đến RAS mà không phải nhảy ở bất cứ đâu. Nếu bạn lặp lại lệnh này, bạn có thể viết các vòng lặp đơn giản khá thuận tiện như &w...k
điều mà tôi đã thực hiện trong các câu trả lời trước đây.
J
giống như j
nhưng không nhớ địa chỉ IP hiện tại trên RAS.
Cũng cần lưu ý rằng RAS lưu trữ không có thông tin về hướng của IP. Vì vậy, trở về một địa chỉ với k
sẽ luôn giữ gìn hiện hướng chỉ IP (và do đó cũng cho dù chúng ta đang ở trong Đức Hồng Y hay chế độ TT) bất kể thế nào chúng ta đi qua j
hoặc w
là đẩy địa chỉ IP ở nơi đầu tiên.
Theo cách đó, hãy bắt đầu bằng cách xem xét chương trình con trong chương trình trên:
01dt,t&w.2,+k
Chương trình con này kéo phần tử dưới cùng của ngăn xếp, n , lên trên cùng và sau đó tính toán các số Fibonacci F (n) và F (n + 1) (để chúng ở trên cùng của ngăn xếp). Chúng ta không bao giờ cần F (n + 1) , nhưng nó sẽ bị loại bỏ bên ngoài chương trình con, do cách &w...k
các vòng lặp tương tác với RAS (loại yêu cầu các vòng lặp này ở cuối chương trình con). Lý do chúng tôi lấy các phần tử từ dưới lên thay vì trên cùng là vì điều này cho phép chúng tôi coi ngăn xếp giống như một hàng đợi hơn, điều đó có nghĩa là chúng tôi có thể tính toán tất cả các số Fibonacci trong một lần mà không phải lưu trữ chúng ở nơi khác.
Đây là cách chương trình con này hoạt động:
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.
Kết thúc của vòng lặp là một chút khó khăn. Miễn là có một bản sao của địa chỉ 'w' trên ngăn xếp, điều này sẽ bắt đầu lần lặp tiếp theo. Một khi những cái đó đã cạn kiệt, kết quả phụ thuộc vào cách chương trình con được gọi. Nếu chương trình con được gọi với 'j', 'k' cuối cùng sẽ trả về đó, vì vậy kết thúc vòng lặp sẽ tăng gấp đôi khi trở lại của chương trình con. Nếu chương trình con được gọi với 'J' và vẫn còn một địa chỉ từ trước đó trên ngăn xếp, chúng ta sẽ nhảy tới đó. Điều này có nghĩa là nếu chương trình con được gọi trong chính vòng lặp bên ngoài, 'k' này sẽ trở về điểm bắt đầu của vòng lặp bên ngoài đó . Nếu chương trình con được gọi với 'J' nhưng RAS hiện đang trống, thì 'k' này không làm gì cả và IP chỉ tiếp tục di chuyển sau vòng lặp. Chúng tôi sẽ sử dụng cả ba trường hợp này trong chương trình.
Cuối cùng, vào chương trình chính nó.
/o....
\i@...
Đây chỉ là hai chuyến du ngoạn nhanh vào chế độ Thông thường để đọc và in số nguyên thập phân.
Sau i
đó, có một w
vị trí nhớ vị trí hiện tại trước khi chuyển sang chương trình con, do vị trí thứ hai /
. Lệnh đầu tiên này của chương trình con tính toán F(n)
và F(n+1)
trên đầu vào n
. Sau đó chúng tôi quay trở lại đây, nhưng chúng tôi đang di chuyển về phía đông, vì vậy phần còn lại của các nhà khai thác chương trình ở chế độ Cardinal. Chương trình chính trông như thế này:
;B1dt&w;31J;d&+
^^^
Ở đây, 31J
là một cuộc gọi khác đến chương trình con và do đó tính toán một số Fibonacci.
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.