앨리스 , 38 36 바이트
2 바이트를 절약 해 준 Leo에게 감사합니다.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
온라인으로 사용해보십시오!
거의 확실하지 않습니다. 제어 흐름은 상당히 정교하고 이전 버전보다 많은 바이트를 절약 한 것에 대해 매우 만족하지만 더 간단 하고 더 많은 것들을 복잡하게 만드는 느낌이 있습니다. 더 짧은 솔루션 .
설명
먼저 Alice의 RAS (Return Address Stack)에 대해 조금 자세히 설명해야합니다. 다른 많은 곰팡이와 마찬가지로 Alice는 코드에서 뛰어 넘을 명령이 있습니다. 그러나 여기에는 원래 위치로 돌아가는 명령도있어 서브 루틴을 매우 편리하게 구현할 수 있습니다. 물론 이것은 2D 언어이므로 서브 루틴은 실제로는 규칙에 의해서만 존재합니다. 리턴 명령 (또는 서브 루틴의 임의의 시점) 이외의 다른 방법을 통해 서브 루틴에 들어가거나 나가는 것을 막을 수있는 것은 없으며, RAS 사용 방법에 따라 클린 점프 / 리턴 계층 구조가 없을 수도 있습니다.
일반적으로 이것은 점프 명령이 점프 j
하기 전에 현재 IP 주소를 RAS로 푸시하도록 함으로써 구현됩니다 . 리턴 명령 k
은 RAS의 주소를 팝하고 점프합니다. RAS가 비어 있으면 k
아무 것도 수행하지 않습니다.
RAS를 조작하는 다른 방법도 있습니다. 이 중 두 가지는이 프로그램과 관련이 있습니다.
w
어디에서든 점프 하지 않고 현재 IP 주소를 RAS로 푸시합니다 . 이 명령을 반복하면 간단한 루프를 매우 편리하게 작성할 수 있습니다 &w...k
. 이전 답변에서 이미 수행했습니다.
J
RAS의 현재 IP 주소 와 j
같지만 기억 나지 않습니다 .
또한 RAS는 IP 방향 에 대한 정보를 저장하지 않습니다 . 와 주소로 반환하는 것은 그래서 k
항상 유지됩니다 현재 에 관계없이 우리가 통과하는 방법 (그러므로 또한 우리가 추기경 또는 서수 모드에있어 여부 등) IP 방향을 j
나w
IP 주소를 처음에 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
하는 거기에 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.