Alice , 38 36 bayt
Leo'ya 2 bayt kaydettiği için teşekkürler.
/ow;B1dt&w;31J
\i@/01dt,t&w.2,+k;d&+
Çevrimiçi deneyin!
Neredeyse kesinlikle optimal değil. Kontrol akışı oldukça ayrıntılıdır ve önceki sürümlere göre kaç bayt tasarruf ettiğinden oldukça memnun olduğum halde, işleri daha basit ve daha kısa bir çözüm olabileceğinden fazla karmaşıklaştırdığımı hissediyorum .
açıklama
İlk olarak, Alice'in dönüş adres yığını (RAS) üzerinde biraz durmam gerekiyor. Diğer birçok fungeoid gibi, Alice de kodda atlamak için bir komuta sahiptir. Ancak, alt rutinleri oldukça rahat bir şekilde uygulamanıza izin veren, nereden geldiğinize geri dönme komutları da vardır. Tabii ki, bu bir 2D dil, alt rutinler gerçekten sadece konvansiyonla var. Geri dönüş komutundan (veya alt programın herhangi bir noktasında) başka bir yolla alt programa girmenizi veya ayrılmanızı engelleyen hiçbir şey yoktur ve RAS'ı nasıl kullandığınıza bağlı olarak, yine de temiz bir atlama / dönüş hiyerarşisi olmayabilir.
Genel olarak bu, atlama komutunun atlamadan j
önce geçerli IP adresini RAS'a iletmesi ile gerçekleştirilir. Dönüş komutu k
daha sonra RAS'ın adresini açar ve oraya atlar. RAS boşsa, k
hiçbir şey yapmaz.
RAS'ı manipüle etmenin başka yolları da vardır. Bunlardan ikisi bu programla ilgilidir:
w
RAS geçerli IP adresini iter olmadan herhangi bir yere atlama. Bu komutu tekrarlarsanız &w...k
, geçmiş döngülerde zaten yaptığım gibi basit döngüler oldukça rahat yazabilirsiniz .
J
benzer j
ancak RAS'taki geçerli IP adresini hatırlamıyor.
RAS'ın IP'nin yönü hakkında hiçbir bilgi depolayamayacağına dikkat etmek de önemlidir . Bu nedenle, bir adrese geri dönmek , IP adresini nasıl geçirdiğimizden veya ilk etapta ittiğimizden bağımsız olarak k
her zaman geçerli IP yönünü (ve dolayısıyla Kardinal veya Sıradan modda olup olmadığımızı) koruyacaktır .j
w
Bu şekilde, yukarıdaki programdaki alt rutine bakarak başlayalım:
01dt,t&w.2,+k
Bu alt-rutinde yığının alt elemanı çekmekte, n , üst ve daha sonra Fibonacci sayıları hesaplar F, (n) ve (n-1 +) F (yığının üstüne bırakmak). Asla F'ye (n + 1) ihtiyacımız yoktur , ancak &w...k
döngülerin RAS ile etkileşimi nedeniyle alt rutinin dışında atılır (bu tür döngülerin bir alt rutinin sonunda olmasını gerektirir ). Üst kısım yerine alttan eleman almamızın nedeni, bu yığını yığına daha çok bir kuyruk gibi işlememize izin vermesidir, bu da tüm Fibonacci numaralarını başka bir yerde saklamak zorunda kalmadan tek seferde hesaplayabileceğimiz anlamına gelir.
Bu altyordam şöyle çalışır:
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.
Döngünün sonu biraz zor. Yığında 'w' adresinin bir kopyası olduğu sürece, bu bir sonraki yinelemeyi başlatır. Bunlar tükendikten sonra, sonuç altyordamın nasıl çağrıldığına bağlıdır. Altyordam 'j' ile çağrılmışsa, son 'k' oraya geri döner, bu nedenle döngü sonu altyordamın geri dönüşü olarak iki katına çıkar. Altyordam 'J' ile çağrılmışsa ve yığın üzerinde daha önce hala bir adres varsa, oraya atlarız. Bu, alt rutinin bir dış ilmeğin kendisinde çağrılması durumunda, bu 'k' o dış ilmeğin başına geri döner . Altyordam 'J' ile çağrılmışsa ancak RAS şimdi boşsa, bu 'k' hiçbir şey yapmaz ve IP döngüden sonra hareket etmeye devam eder. Bu vakaların üçünü de programda kullanacağız.
Son olarak, programın kendisine.
/o....
\i@...
Bunlar, ondalık tam sayıları okumak ve yazdırmak için Ordinal moda yalnızca iki hızlı gezidir.
Sonra, ikinci nedeniyle alt rutine geçmeden önce mevcut pozisyonu hatırlayan i
bir var . Altprogramın Bu ilk başlatılmasını hesaplar ve girişi . Daha sonra buraya atlıyoruz, ama şimdi doğuya hareket ediyoruz, bu yüzden Kardinal modunda program operatörlerinin geri kalanı. Ana program şöyle görünür:w
/
F(n)
F(n+1)
n
;B1dt&w;31J;d&+
^^^
Burada, 31J
altyordam için bir başka çağrı ve bu nedenle bir Fibonacci numarası hesaplar.
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.