解 PWC 080:以 Raku、Perl、Kotlin
作者:gugod 發佈於: ,更新於: #perl #raku #kotlin晚上看了 PWC 080 的兩個題目,以 Raku、Perl、Kotlin 三種語言來試解。原則上是想要不使用 for / while 迴圈,以各語言中提供的 collection 來解成。
以第一題來說,Raku 與 Kotlin 有 Set
這種無序集合物件可用,而且可以一步把列表轉成集合,比起 Perl 而言算是比較能表意。以下是三種語言併列,把命令列參數轉換成「整數」後,做成集合的步驟:
Kotlin
val N = args.map({ it.toIntOrNull() }).filter({ it != null });
val seen = N.toSet();
Raku
my @N = @*ARGS.map({ .Int })
my $seen = @N.Set();
Perl
my @N = map { int($_) } @ARGV;
my %seen = map { $_ => 1 } @N;
寫 Perl 寫熟練了,很容易就能看出 map { $_ => 1 }
就是要拿來做出 HashSet
物件來用。不過,相對於其他兩都似乎就是少了些字面上的線索。
第二題的題目是要發糖果給 N 個人,先每人發一顆,再根據輸入 @N
陣列的內容多發幾根。說明文看來有點長,但其實不複雜。其規則原文如下:
a) You must given at least one candy to each candidate.
b) Candidate with higher ranking get more candies than their mmediate neighbors on either side.
輸入陣列 @N
的內容數字所代表的是每位人選的權重,若某人之權重數字比其隔壁兩人都高,則該人多得兩根,若只比其中一人高,則多得一顆。
雖然乍看之下對於每人 @N[$i]
,需要先後檢查 @N[$i - 1]
與 @N[$i + 1]
,還得同時注意不能超過陣列邊界。但其實有比較容易的處理方式。
如果把這問題轉換成去檢查數字間的「間隔」的話,就比較容易理解,也不必處理邊際條件。先把兩間隔數字之間的關係寫出來如下:
1 < 2 < 3 > 1 = 1 = 1
然後由左至右逐一看過符號,只要符號是 =
,就表示左右兩方的人選都不會因此而多拿到一顆糖果。如果是大於(或小於),則表示其左方(或右方)的人選會多拿到一顆糖果。一個符號會對應到零根或一顆糖果。換句話說,只要符號兩端的數字彼此不相等(符號不是 =
),總糖果數就要多一顆。
求額外發出的糖果數目這部分解法在各語言對照如下:
Kotlin:
val extra = (1..N.lastIndex).filter({ N[it] != N[it-1] }).size;
Raku:
my $extra = (1..@N.end).grep(-> $i { @N[$i] != @N[$i-1] }).elems;
Perl:
my $extra = grep { @N[$_] != @N[$_-1] } (1..@N-1);
Perl 由於有純量語境,相對於其他兩者少了個最後表示「取得個數」的字樣。
Raku 中用來表示陣列最尾端索引的函式為 .end
,跟 Kotlin 採用的 .lastIndex
比起來短了不少。不知會不會被誤認為其意義是「最後一個元素內容」。附帶一提,陣列 N
最後最後一個元素內容,在 Kotlin 中表示為 N.last()
,在 Raku 中表示為 @N.tail
。
這幾個名詞與其對應的意義其實都還算好記。附帶一提,對我而言會常常忘記的是 Kotlin 中 .size
是屬性而非函式,偶爾還是會不小心寫出 N.size()
然後發生編譯錯誤。