Sneaking into a loop
[Sneaking into a loop]https://gfldex.wordpress.com/2016/08/10/sneaking-into-a-loop/
Zoffix 回答了一個關于 Perl 5s <> 操作符的問題。
slurp.words.Bag.sort(-*.value).fmt("%10s3d\n").say;
slurp
會從 STDIN 中讀取整個 "file" 并返回一個 Str。方法 Str::words
會按照某種 Unicode 意義的單詞把該字符串分割成一個列表。把列表強轉為 Bag 則創建一個計數 Hash, 它是如下表述的快捷方式。
my %h;
%h{$_}++ for <peter paul marry>;
dd %h;
# # OUTPUT?Hash %h = {:marry(1), :paul(1), :peter(1)}
?
在關聯數組上調用 .sort(-*.value)
會按照值的降序排序并返回一個排序后的 Pairs 列表。List::fmt 會調用 Pair::fmt, 它調用 fmt 方法, .key 作為其第二個參數, .value 也作為參數。say 會會使用一個空格連接各個元素并輸出到標準輸出。最后一步有一點錯誤因為除了第一行之外的每一行前面都會有一個額外的空格。
slurp.words.Bag.sort(-*.value).fmt("%10s => %3d").join("\n").say;
手動連接字符串更好。這對于簡短的單行程序來說有點多了。我們需要找到最長的單詞并使用 .chars
來獲取列寬。
slurp 會在 $*IN
身上調用 .slurp-rest
方法。
$*IN = <peter paul marry peter paul paul> but role { method slurp-rest { self.Str } };
這是一種 hack 因為它會在任何形式的類型檢測上失敗并且它除了 slurp 之外不會對任何東西起作用。還有, 實際上我們從 $*IN
那里解綁 STDIN。不要在工作中使用這個奇淫技巧。
現在我們能開心地吞噬并開始計數了。
my %counted-words = slurp.words.Bag;
my $word-width = [max] %counted-words.keys?.chars;
并且繼續在鏈子斷開的地方繼續。
%counted-words.sort(-*.value).fmt("%{$word-width}s3d").join("\n").say;
問題解決了但是很丑陋。我們把一個單行程序拆開了。我們來修復 fmt 以使它再次完整。
我們想要的是一個 fmt 方法, 它接收一個位置的(Positional), 一個 printf 風格的格式字符串和一個格式字符串中的 block per %*
。還有, 我們可能需要在 self.fmt 前面放上一個分隔符。
my multi method fmt(Positional:D: $fmt-str, *@width where *.all ~~ Callable, :$separator = " "){
self.fmt(
$fmt-str.subst(:g, "%*", {
my &width = @width[$++] // Failure.new("missingh block");
'%' ~ (&width.count == 2 ?? width(self, $_) !! width(self))
}), $separator);
}
表達式 *.all ~~ Callable
檢查 slurp array中的所有元素是否實現了 CALL-ME(那是實際被執行的方法在你執行 foo()的時候)。
然后我們在格式字符串上使用了 subst
來替換 %*
, 替換是一個(閉包)塊兒, 它每次匹配被調用一次。而且這兒我們有不錯的慣用法。
say "1-a 2-b 3-c".subst(:g, /\d/, {<one two three>[$++]});
# one-a two-b three-c
匿名狀態變量 $
從 0 開始計數, 每次代碼塊執行時增 1。實際上我們在這兒做的就是移除一個循環并給 subst 偷偷加入一個額外的計數器和數組下標?;蛘呖梢哉f我們注冊了一個迭代器到 subst 里面的循環中。有人可能會質疑 subst 應該接收一個 Seq 作為它的第二個位置參數, 它會讓調用變得冗長。無論如何, 我們把洞補上了。
在第 11 行, 我們從吞噬數組中拿出一個元素或者在沒有元素時創建一個 Failure。我們把 block 存儲在一個變量中因為我們想在第 12 行中內省。如果那個 block 接收兩個位置參數,we feed the topic subst is calling the block with as a 2nd parameter to our stored block. 那碰巧是一個 Match 并且對于影響所匹配的東西可能有用。在我們這個例子中我們對 %*
進行匹配并且當前位置由 $++
計數。做完那個之后我們得到了一個格式字符串, 它帶有一個由用戶提供的 fmt 版本的列寬參數。
用戶提供的塊兒使用一組 Pairs 調用。我們不得不深入一層以得到更大的鍵。
{[max] .values?.keys?.chars}
得到第一列的列寬。
print %counted-words.sort(-*.value).&fmt("%*s3d", {[max] .values?.keys?.chars}, separator => "\n");
那個時髦的 .&fmt
調用是必須的因為我們免費的浮點方法不是 List 的方法。
-- 翻譯的好爛。