[Raku] 如何取得陣列切片
作者:gugod 發佈於: #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。