[Raku] 如何取得陣列切片

作者:   發佈於:   #rakulang #howto

所謂陣列切片,指的是現有陣列中的一部分內容的意思。

設有整數陣列 @a 內容如下:

my Int @a = (0, 10, 20, 30, 40, 50, 60, 70, 80, 90);

若要取得特定幾個位置的元素,只要一一指定位置即可:

my Int @b = @a[ 5, 3, 1, 9 ];
say @b; #=> [50, 30, 10, 90]

若要取自 30 到 70 爲止的這段切片,可直接在取元素時給定一個範圍:

my Int @b = @a[ 3..7 ];  #=> [30, 40, 50, 60, 70]

3..7 這種範圍表示法是頭尾兩端皆包含。如果不要包含頭或尾,可在 .. 前後加上 ^ 符號:

my Int @c = @a[ 3..^7 ]; #=> [30, 40, 50, 60]
my Int @d = @a[ 3^..^7 ]; #=> [40, 50, 60]

如果指定範圍超出陣列邊界的話,在超界的位置會得到未定義值。也就是陣列的切片,其長度必定與指定範圍的長度相同。如果不要那些空的值的話,必需以 .grep 將其移除。例:

my Int @b = @a[ 9..12 ];
say @b; #=> [90 (Int) (Int) (Int)]

my Int @c = @a[ 9..12 ].grep(&defined);
say @c; #=> [90]

(由於 @a@b 型別都限制爲 Int,所以 raku 以 (Int) 這種寫法來表示出未定義值)

但有 2 個特殊值不會讓未定義值出現在切片內。這兩個特殊值是 * (也可以 Inf 表示)。如果切片範圍尾端是 *,那切片只會取到原陣列的尾端,不會有無限多個未定義值跟在後面。

my Int @d = @a[ 9 .. * ];
say @d; #=> [90]

my Int @e = @a[ 9 .. ∞ ];
say @e; #=> [90]

這符號表示「無限大」,所有能裝在 Num 型別裡面的數值都比 還小。而 * 這符號的意義接近口語上「隨便啦」的意義,依算符,對應到某個「常用且有道理」的值。用在範圍的尾端是就等同於 ,用在範圍的起始端時則等同於 -∞(負無限大)。這符號的的型別是 Whatever

此外,陣列型別有個 .splice 函式,可以把陣列中一段範圍移除。並把空出來的範圍以後方的元素遞補。.splice 的兩個參數分別是代表起始位置與長度。例如 @a.splice(3, 5) 表示取出 @a 的第 3 個位置之後的 5 個元素,也就是放在 3, 4, 5, 6, 7 這五個位置的元素。例如:

my Int @a = (0, 10, 20, 30, 40, 50, 60, 70, 80, 90);
my Int @e = @a.splice(3, 5);

say @a; #=> [0 10 20 80 90]
say @e; #=> [30 40 50 60 70]

如果切片範圍頭尾是兩個變數,不太容易用人腦換算成起始值與長度的話,可先把範圍存於陣列中,再利用 .first.elems 函式來讓電腦計算出來。例如以下以 $begin$end 兩變數來代表範圍頭尾:

my Int @a = (0, 10, 20, 30, 40, 50, 60, 70, 80, 90);

my ($begin, $end) = (3, 7);

my @range = ($begin .. $end);

say @a[ @range ]; #=> (30 40 50 60 70)

my Int @b = @a.splice( @range.first, @range.elems );

say @a; #=> [0 10 20 80 90]
say @b; #=> [30 40 50 60 70]

交給電腦來換算的話,或許可以避開不少 off-by-one bug。