內建於 Raku 語言中數列產生器

發佈於:

幾天前 @liz 回應了我的某則推文 ,內容是將我的一小段程式碼改寫成較有「Raku 風格」的版本。

原文:https://twitter.com/liztormato/status/1241460318167072770

我推文中所寫的程式碼為:

$ raku -e 'my $rate = 1.35; my $n = 153; for 1..14 { $n = ceiling($n * $rate); say $n }' 

其輸出為:

207 280 379 512 692 935 1263 1706 2304 3111 4200 5671 7656 10336

這段程式的基本上就是產生一個等比數列,首項為 153,公比為 1.35。不過,每步中多包了個 ceiling 函式,使得每項數字比實際結果大一點。到數列後端,誤差累計也愈多。

她所提供的改寫版程式碼如下:

$ raku -e 'say (153, { ceiling($^n * 1.35) } ... *)[1..14]'

其輸出為:

(207 280 378 511 690 932 1259 1700 2295 3099 4184 5649 7627 10297)

可看到輸出的內容略有不同。

我想 @liz 所謂的「Raku 風格」,所指的就是:使用了惰性資料結構與數列產生器這兩件事吧。

這段程式主要核心部份是個 infix ... 算符,此算符能做出數列產生器或數列本身。視極值是否為 Inf* 而定。比方說以下這幾例,就是數列本身:

$ raku -e 'say 1...5'
(1 2 3 4 5)

> raku -e 'say 1...500'
(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 ...)

> raku -e 'my @s = 1...50000; say @s.elems; say @s'
50000
[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 ...]

而以下幾例,定義本身就是無窮數列,所以做出來的是數列產生器(在 Raku 語言中的概念為 lazy list(懶惰的列表)):

> raku -e 'say 10...*'
(...)

> raku -e 'say *..100'
-Inf..100

> raku -e 'my @s = 100..*; say @s[304]; say @s.elems'
404
Cannot .elems a lazy list
  in block <unit> at -e line 1

前述最後一例中, say @s.elems 印出了錯誤訊息。表示 @slazy list,因此 .elems 方法之調用是不被允許的。

而這 infix ... 算符,其實是可以直接以少量參數來生成等差數例與等比數列的。這部份由編譯器自動偵測,但在程式碼中需提供前三項的數字,以及上限:

等差級數之例:

> raku -e 'say 1,3,5...24'
(1 3 5 7 9 11 13 15 17 19 21 23)
> raku -e 'my @s = 7,24,41...Inf; say @s[0..30]'
(7 24 41 58 75 92 109 126 143 160 177 194 211 228 245 262 279 296 313 330 347 364 381 398 415 432 449 466 483 500 517)

(前例中的 0..30,所用的算符為 infix ..,與 infix ... 有一點不同。)

等比級數之例:

> raku -e 'say 1,3,9...10000'
(1 3 9 27 81 243 729 2187 6561)

> raku -e 'my @s = 1,1,1...Inf; say @s[0...30]'
(1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1)

> raku -e 'my @s = 1,2,4...Inf; say @s[10,20,30,40]'
(1024 1048576 1073741824 1099511627776)

而 @liz 這段程式碼,則是使用了自定的產生器函式,就是以下的 { ceiling($^n * 1.5) } 這部份。

$ raku -e 'say (153, { ceiling($^n * 1.35) } ... *)[1..14]'

自定產生器函式可以用來產生任意的數列了。比方說費式數列

> raku -e 'my @s = 1, 1, { $^a + $^b } ... Inf; say @s[0..15];'
(1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987)

{...} 區塊左方列出的數字們為函式的初始引數,可以有很多個數字。右方則為數列極值。比方說以下是個由前四項數字計算出本項的變種費式數列:

> raku -e 'my @s = 1, 1, 1, 1, { $^a + $^b + $^c + $^d } ... *; say @s[0..15];'
(1 1 1 1 4 7 13 25 49 94 181 349 673 1297 2500 4819)

介紹大致上至此。最後,既然提到費式數列,那就以逼近黃金比例之計算過程做為結論吧:

> raku -e 'my @s = 1,1,{ $^a + $^b }...Inf; say @s[1..25] >>/<< @s[0..24]'
(1 2 1.5 1.666667 1.6 1.625 1.615385 1.619048 1.617647 1.618182 1.617978 1.618056 1.618026 1.618037 1.618033 1.618034 1.618034 1.618034 1.618034 1.618034 1.618034 1.618034 1.618034 1.618034 1.618034)