如何在 Raku 中循环哈希的排序(使用自定义排序)键?
How to loop on sorted (with custom sort) keys of a hash in Raku?
尝试逐步将一些 Perl 脚本转换为 Raku。即使在此处浏览了很多内容并更深入地阅读了 Learning Perl 6 后,我仍然坚持以下内容。
我无法取得进展的部分是最后一个循环(转换为for
);获取密钥并按月份名称和日期编号对它们进行排序看起来不可能,但我相信它是可行的。
非常欢迎任何关于如何使用“惯用”语法实现此目的的提示。
#!/usr/bin/perl
use strict;
my %totals;
while (<>) {
if (/redis/ and /Partial/) {
my($f1, $f2) = split(' ');
my $w = $f1 . ' ' . $f2;
$totals{$w}++;
}
}
my %m = ("jan" => 1, "feb" => 2, "mar" => 3, "apr" => 4, "may" => 5, "jun" => 6,
"jul" => 7, "aug" => 8, "sep" => 9, "oct" => 10, "nov" => 11, "dec" => 12);
foreach my $e (sort { my($a1, $a2) = split(' ', $a) ; my($b1, $b2) = split(' ', $b) ;
$m{lc $a1} <=> $m{lc $b1} or $a2 <=> $b2 } keys %totals) {
print "$e", " ", $totals{$e}, "\n";
}
您可以尝试类似的方法:
enum Month (jan => 1, |<feb mar apr may jun jul aug sep oct nov dec>);
lines()
andthen .grep: /redis/&/Partial/
andthen .map: *.words
andthen .map: {Month::{.[0].lc} => .[1].Int}\
#or andthen .map: {Date.new: year => Date.today.year, month => Month::{.[0].lc}, day => .[1], }\
andthen bag $_
andthen .sort
andthen .map: *.put;
输入相同的示例数据,您的 perl 代码会产生与此相同的输出。
my $data = q:to/END/;
may 01 xxx3.1 Partial redis
may 01 xxx3.2 Partial redis
may 01 xxx3.3 Partial redis
apr 22 xxx2.2 Partial redis
apr 22 xxx2.1 Partial redis
mar 01 xxx1 redis Partial
some multi-line
string
END
sub sort-by( $value )
{
state %m = <jan feb mar apr may jun jul aug sep oct nov dec> Z=> 1..12;
%m{ .[0].lc }, .[1] with $value.key.words;
}
say .key, ' ', .value.elems
for $data
.lines
.grep( /redis/ & /Partial/ )
.classify( *.words[0..1].Str )
.sort( &sort-by );
我认为这与您的要求很接近...还表明 perl6/raku 与 perl5 非常密切相关,除非您想要花哨的...
#test data...
my %totals = %(
"jan 2" => 3,
"jan 4" => 1,
"feb 7" => 1,
);
my %m = %("jan" => 1, "feb" => 2, "mar" => 3, "apr" => 4, "may" => 5, "jun" => 6,
"jul" => 7, "aug" => 8, "sep" => 9, "oct" => 10, "nov" => 11, "dec" => 12);
my &sorter = {
my ($a1, $a2) = split(' ', $^a);
my ($b1, $b2) = split(' ', $^b);
%m{lc $a1} <=> %m{lc $b1} or $a2 <=> $b2
}
for %totals.keys.sort(&sorter) ->$e {
say "$e => {%totals{$e}}"
}
#output
jan 2 => 3
jan 4 => 1
feb 7 => 1
主要变化是:
- %totals{$e} 为 $totals{$e}
- %() 而不是哈希文字的 {}
- for with method syntax and -> 而不是 foreach with sub syntax
- 排序例程中的 $^a 和 $^b 需要插入符号 (^)
- say 比 print 更干净
TL;DR @wamba 提供了一个惯用的解决方案。这个答案是一个最小的“机械” t运行slation。
我认为你的问题和这个答案表明,学习许多与 Perl 相关的 Raku 基础知识的好方法是:
将一个小的 Perl 程序输入 Rakudo;
有条不紊地investigate/fix每次报告错误直到它起作用;
Post 如果遇到困难,请向 Whosebug 提问。
假设这就是您所做的,太好了。如果没有,希望这个答案能激励您或其他读者尝试这样做。
密码
my %totals;
for lines() {
if (/redis/ and /Partial/) {
my ($f1, $f2) = split(' ', $_);
my $w = $f1 ~ ' ' ~ $f2;
%totals{$w}++;
}
}
my %m = ("jan" => 1, "feb" => 2, "mar" => 3, "apr" => 4, "may" => 5, "jun" => 6,
"jul" => 7, "aug" => 8, "sep" => 9, "oct" => 10, "nov" => 11, "dec" => 12);
for sort { my ($a1, $a2) = split(' ', $^a) ; my ($b1, $b2) = split(' ', $^b) ;
%m{lc $a1} <=> %m{lc $b1} or $a2 <=> $b2 }, keys %totals
-> $e {
print "$e", " ", %totals{$e}, "\n";
}
适用于以下测试输入:
feb 1 redis Partial
jan 2 Partial redis
jan 2 redis Partial
机械化运行化过程
通过将你问题中的代码提供给 Rakudo,我开始正确地处理你的问题。并惊讶地得到Unsupported use of <>. ...
。这是 Perl 代码,不是 Rakunian!
然后我看到@wamba 提供了一个惯用的解决方案。我决定改用最直接的 t运行slation。我的第一次尝试成功了。秩序恢复。
我考虑了如何最好地解释我的更改。我想知道如果我回到开始并一次只修复一个错误消息会是什么。结果是一系列令人愉快的良好错误消息。因此,我将此答案的其余部分构造为一系列错误 messages/fixes/discussions,每个错误都会导致下一个错误,直到程序正常运行。
为了简单起见,我删除了错误消息中的大部分信息。 messages/fixes 是我遇到的顺序,一次修复一个:
-
Unsupported use of <>. In Raku please use: lines() to read input ...
------> while (<⏏>) {
(⏏
是 Unicode 的弹出符号,标记编译器在概念上“弹出”代码的位置。)
Perl 的 while (<>)
的惯用替换是 for lines()
。
-
Variable '$f1' is not declared
------> my(⏏$f1, $f2) ...
Raku 将 foo(...)
形式的代码解释为函数调用,如果它是函数调用有意义的地方。这优先于将 foo
解释为关键字(即 my
作为变量声明符)。
接下来,因为 my($f1, $f2)
被解释为函数调用,所以 $f1
被解释为您尚未声明的参数,导致出现错误消息。
在 my
之后插入空格可以解决实际问题和这个明显的问题。
(此错误发生在您代码中的多个位置;我每次都应用了相同的修复程序。)
-
Unsupported use of . to concatenate strings. In Raku please use: ~.
------> my $w = $f1 .⏏ ' ' . $f2;
为了帮助记住 ~
在 Raku 中用作字符串操作,请注意它看起来像一段字符串。
-
Variable '$totals' is not declared. Did you mean '%totals'?
------> ⏏$totals{$w}++;
正如 Damian Conway 所说,"We took this Perl table of what do you use when, and we made it this table instead"。
代码 $totals{...}
语法上 有效。一个 可以 将散列(引用)绑定或分配给标量。但是 Rakudo(Raku 编译器)在编译时知道代码没有声明一个 $totals
变量,所以它正确地抱怨。
您的代码 已 声明了一个 %totals
变量,因此 Rakudo 会很有帮助地询问您是否是这个意思。
(此错误发生在您代码中的多个位置;我每次都应用了相同的修复程序。)
-
Unsupported use of 'foreach'. In Raku please use: 'for'.
------> foreach⏏ my
Raku code tends to be shorter (and more readable) than Perl code。这主要是由于设计超越了单纯的油漆,但像 s/for/foreach
这样的小东西不会造成伤害。
-
This appears to be Perl code
------> for ⏏my $e
此错误消息可以说是 LTA(我认为“非描述性”)。但考虑到所有因素,它同样可以说相当不错。
Perl 和 Raku 支持将值绑定到范围为块的新词法 variable/parameter。 Perl 使用 my
,并将变量放在值的前面。 Raku 将值放在首位,在它们和任何变量之间插入一个 ->
,并跳过 my
.
(->
的使用有很多丰富性,我不会在这里讨论,因为这对这个例子来说无关紧要。但值得一提的是,这一变化为 Rakoons 带来了很划算,值得您期待。)
-
Variable '$a' is not declared
------> for sort { my ($a1, $a2) = split(' ', ⏏$a)
正如 Perl 开发者所知,它有特殊的变量 $a
和 $b
.
Raku 将此概念概括为 $^foo
parameters, a convenient DRY 将位置参数添加到闭包的方式,同时跳过通常的仪式,在仪式中必须指定两次名称(一次声明它,另一次使用它)。
它们的一个不寻常的方面是,它们的形式参数位置由它们的名称决定,最初可能看起来很疯狂,但实际上非常符合人体工程学。因此,给定 $^foo
和 $^bar
,后者将绑定到 第一个 位置参数,$^foo
到第二个
-
Missing comma after block argument to sort
------> { ... }⏏ keys
我在指定的地方插入了一个逗号。
-
Calling split(Str) will never work with signature of the proto ($, $, |)
------> my ($f1, $f2) = ⏏split(' '
一些 Perl 例程隐含地假定使用 $_
。没有 syntactic 方法可以知道任何给定的例程是否隐含地使用它。您只需要阅读每个例程的定义。 Raku 放弃了那个。
因此 Rakudo 得出结论,split
例程缺少要拆分的字符串。
(“原型签名($,$,|)”中的|
只是表示“可以可选传递的其他参数”,所以你可以忽略它。$, $
表示需要 两个 个参数,所以我们缺少一个。)
对例程定义的快速检查显示 split
的 sub
版本要求将字符串拆分为其 second 位置参数。因此我切换到 split(' ', $_)
.
代码有效。 \o/
值得注意的是,所有 实际错误消息都以 ===SORRY!=== Error while compiling
开头。这意味着他们甚至都在程序 运行 之前就被抓住了,这很好。
你已经得到了很好的答案,但我想借此机会向你介绍一些其他标准的 Raku 工具和习惯用法,这些工具和习语对我来说似乎很适合你的问题。
对于我的两个解决方案:
我的 %totals
变量以结构化数据形式存储键,而不仅仅是字符串键。假定的基本原理是简化排序和表示。 (但这实际上是为了向您展示另一种方式。确保将月份和日期数字连接为两个两位数字以确保正确排序当然是微不足道的。)我使用两种不同的键类型来显示这个主题的变化。
我通过构建将名称映射到数字的哈希来处理 to/from 月份名称的转换。我用 .pairs
或 .antipairs
方法声明一个,然后应用反向转换到另一个方向。我在第一个解决方案中以一种方式执行此操作,在第二个解决方案中以另一种方式执行此操作。我在一个解决方案中将 jan
的数字设置为 0
,在另一个解决方案中将 1
的数字设置为
短小甜美,靠Pair
s
声明%foo
变量时,如果不指定其键类型,则默认为Str
。但是在这段代码中,%totals
中每个Pair
的key本身就是一个Pair
:
my %totals{Pair}; # Keys of the `Pair`s in `%totals` are themselves `Pair`s
my %months = <jan feb mar apr may jun jul aug sep oct nov dec> .pairs; # 0 => jan
for lines.grep(/redis/ & /Partial/)».words {
++%totals{ %months.antipairs.hash{ lc .[0] }.Int => .[1].Int }
}
for %totals .sort {
printf "%3s %2d : %-d\n", %months{.key.key}, .key.value, .value
}
如果未指定排序闭包,Raku 的 sort
例程在应用于散列时会通过使用 cmp
比较它们的键来对其条目进行排序。此外,对于普通哈希,比较两个键意味着比较两个字符串。
如果这些字符串是每个日期的月份和日期,每个都格式化为两位数,然后连接起来,那么这对您的情况来说会很好。或者,拆分和 schwartzian 也可以正常工作。 Raku 真的很擅长这些,但我更喜欢用不同的方式来回答这个问题,所以默认的排序是正确的。
对于第一个解决方案,我选择 Pair
s 作为密钥类型。当 cmp
比较 Pair
s 时,它首先按键排序,然后按其中的值排序。键和值都被强制为 Int
s,因此上面的代码正确地按月排序,然后是其中的天数。
更多结构,使用Date
s
此版本增加了结构和更花哨的打字。它将 %totals
散列的等效项(重命名为 %.data
)包装到包含一些实用程序的外部对象中,并使内部键对象成为 Date
而不是 Pair
:
role Totals {
my %months = <jan feb mar apr may jun jul aug sep oct nov dec> .antipairs «+» 1; # jan => 1
method month-name (Int $num --> Str) { %months.antipairs.hash{$num} }
method month-num (Str $name --> Int) { %months{lc $name} }
has %.data{Date} handles <sort>;
}
my $totals = Totals.new;
for lines.grep(/redis/ & /Partial/)».words {
++$totals.data{ Date.new: :year(2000), :month(Totals.month-num: .[0]), :day(.[1]) }
}
for $totals .sort {
printf "%3s %2d : %-d\n", Totals.month-name(.key.month), .key.day, .value
}
在第一个解决方案中,sort
做了正确的事情,因为它正在比较 Pair
s,而 cmp
反过来做了正确的事情,因为我是如何设置这些对的向上。
在这个解决方案中 sort
/cmp
做正确的事情而不需要将字符串值强制为 Int
s,因为总计条目是 Date
s 并且它们按普通日期比较规则比较
尝试逐步将一些 Perl 脚本转换为 Raku。即使在此处浏览了很多内容并更深入地阅读了 Learning Perl 6 后,我仍然坚持以下内容。
我无法取得进展的部分是最后一个循环(转换为for
);获取密钥并按月份名称和日期编号对它们进行排序看起来不可能,但我相信它是可行的。
非常欢迎任何关于如何使用“惯用”语法实现此目的的提示。
#!/usr/bin/perl
use strict;
my %totals;
while (<>) {
if (/redis/ and /Partial/) {
my($f1, $f2) = split(' ');
my $w = $f1 . ' ' . $f2;
$totals{$w}++;
}
}
my %m = ("jan" => 1, "feb" => 2, "mar" => 3, "apr" => 4, "may" => 5, "jun" => 6,
"jul" => 7, "aug" => 8, "sep" => 9, "oct" => 10, "nov" => 11, "dec" => 12);
foreach my $e (sort { my($a1, $a2) = split(' ', $a) ; my($b1, $b2) = split(' ', $b) ;
$m{lc $a1} <=> $m{lc $b1} or $a2 <=> $b2 } keys %totals) {
print "$e", " ", $totals{$e}, "\n";
}
您可以尝试类似的方法:
enum Month (jan => 1, |<feb mar apr may jun jul aug sep oct nov dec>);
lines()
andthen .grep: /redis/&/Partial/
andthen .map: *.words
andthen .map: {Month::{.[0].lc} => .[1].Int}\
#or andthen .map: {Date.new: year => Date.today.year, month => Month::{.[0].lc}, day => .[1], }\
andthen bag $_
andthen .sort
andthen .map: *.put;
输入相同的示例数据,您的 perl 代码会产生与此相同的输出。
my $data = q:to/END/;
may 01 xxx3.1 Partial redis
may 01 xxx3.2 Partial redis
may 01 xxx3.3 Partial redis
apr 22 xxx2.2 Partial redis
apr 22 xxx2.1 Partial redis
mar 01 xxx1 redis Partial
some multi-line
string
END
sub sort-by( $value )
{
state %m = <jan feb mar apr may jun jul aug sep oct nov dec> Z=> 1..12;
%m{ .[0].lc }, .[1] with $value.key.words;
}
say .key, ' ', .value.elems
for $data
.lines
.grep( /redis/ & /Partial/ )
.classify( *.words[0..1].Str )
.sort( &sort-by );
我认为这与您的要求很接近...还表明 perl6/raku 与 perl5 非常密切相关,除非您想要花哨的...
#test data...
my %totals = %(
"jan 2" => 3,
"jan 4" => 1,
"feb 7" => 1,
);
my %m = %("jan" => 1, "feb" => 2, "mar" => 3, "apr" => 4, "may" => 5, "jun" => 6,
"jul" => 7, "aug" => 8, "sep" => 9, "oct" => 10, "nov" => 11, "dec" => 12);
my &sorter = {
my ($a1, $a2) = split(' ', $^a);
my ($b1, $b2) = split(' ', $^b);
%m{lc $a1} <=> %m{lc $b1} or $a2 <=> $b2
}
for %totals.keys.sort(&sorter) ->$e {
say "$e => {%totals{$e}}"
}
#output
jan 2 => 3
jan 4 => 1
feb 7 => 1
主要变化是:
- %totals{$e} 为 $totals{$e}
- %() 而不是哈希文字的 {}
- for with method syntax and -> 而不是 foreach with sub syntax
- 排序例程中的 $^a 和 $^b 需要插入符号 (^)
- say 比 print 更干净
TL;DR @wamba 提供了一个惯用的解决方案。这个答案是一个最小的“机械” t运行slation。
我认为你的问题和这个答案表明,学习许多与 Perl 相关的 Raku 基础知识的好方法是:
将一个小的 Perl 程序输入 Rakudo;
有条不紊地investigate/fix每次报告错误直到它起作用;
Post 如果遇到困难,请向 Whosebug 提问。
假设这就是您所做的,太好了。如果没有,希望这个答案能激励您或其他读者尝试这样做。
密码
my %totals;
for lines() {
if (/redis/ and /Partial/) {
my ($f1, $f2) = split(' ', $_);
my $w = $f1 ~ ' ' ~ $f2;
%totals{$w}++;
}
}
my %m = ("jan" => 1, "feb" => 2, "mar" => 3, "apr" => 4, "may" => 5, "jun" => 6,
"jul" => 7, "aug" => 8, "sep" => 9, "oct" => 10, "nov" => 11, "dec" => 12);
for sort { my ($a1, $a2) = split(' ', $^a) ; my ($b1, $b2) = split(' ', $^b) ;
%m{lc $a1} <=> %m{lc $b1} or $a2 <=> $b2 }, keys %totals
-> $e {
print "$e", " ", %totals{$e}, "\n";
}
适用于以下测试输入:
feb 1 redis Partial
jan 2 Partial redis
jan 2 redis Partial
机械化运行化过程
通过将你问题中的代码提供给 Rakudo,我开始正确地处理你的问题。并惊讶地得到Unsupported use of <>. ...
。这是 Perl 代码,不是 Rakunian!
然后我看到@wamba 提供了一个惯用的解决方案。我决定改用最直接的 t运行slation。我的第一次尝试成功了。秩序恢复。
我考虑了如何最好地解释我的更改。我想知道如果我回到开始并一次只修复一个错误消息会是什么。结果是一系列令人愉快的良好错误消息。因此,我将此答案的其余部分构造为一系列错误 messages/fixes/discussions,每个错误都会导致下一个错误,直到程序正常运行。
为了简单起见,我删除了错误消息中的大部分信息。 messages/fixes 是我遇到的顺序,一次修复一个:
-
Unsupported use of <>. In Raku please use: lines() to read input ... ------> while (<⏏>) {
(
⏏
是 Unicode 的弹出符号,标记编译器在概念上“弹出”代码的位置。)Perl 的
while (<>)
的惯用替换是for lines()
。
-
Variable '$f1' is not declared ------> my(⏏$f1, $f2) ...
Raku 将
foo(...)
形式的代码解释为函数调用,如果它是函数调用有意义的地方。这优先于将foo
解释为关键字(即my
作为变量声明符)。接下来,因为
my($f1, $f2)
被解释为函数调用,所以$f1
被解释为您尚未声明的参数,导致出现错误消息。在
my
之后插入空格可以解决实际问题和这个明显的问题。(此错误发生在您代码中的多个位置;我每次都应用了相同的修复程序。)
-
Unsupported use of . to concatenate strings. In Raku please use: ~. ------> my $w = $f1 .⏏ ' ' . $f2;
为了帮助记住
~
在 Raku 中用作字符串操作,请注意它看起来像一段字符串。
-
Variable '$totals' is not declared. Did you mean '%totals'? ------> ⏏$totals{$w}++;
正如 Damian Conway 所说,"We took this Perl table of what do you use when, and we made it this table instead"。
代码
$totals{...}
语法上 有效。一个 可以 将散列(引用)绑定或分配给标量。但是 Rakudo(Raku 编译器)在编译时知道代码没有声明一个$totals
变量,所以它正确地抱怨。您的代码 已 声明了一个
%totals
变量,因此 Rakudo 会很有帮助地询问您是否是这个意思。
(此错误发生在您代码中的多个位置;我每次都应用了相同的修复程序。)
-
Unsupported use of 'foreach'. In Raku please use: 'for'. ------> foreach⏏ my
Raku code tends to be shorter (and more readable) than Perl code。这主要是由于设计超越了单纯的油漆,但像
s/for/foreach
这样的小东西不会造成伤害。
-
This appears to be Perl code ------> for ⏏my $e
此错误消息可以说是 LTA(我认为“非描述性”)。但考虑到所有因素,它同样可以说相当不错。
Perl 和 Raku 支持将值绑定到范围为块的新词法 variable/parameter。 Perl 使用
my
,并将变量放在值的前面。 Raku 将值放在首位,在它们和任何变量之间插入一个->
,并跳过my
.(
->
的使用有很多丰富性,我不会在这里讨论,因为这对这个例子来说无关紧要。但值得一提的是,这一变化为 Rakoons 带来了很划算,值得您期待。)
-
Variable '$a' is not declared ------> for sort { my ($a1, $a2) = split(' ', ⏏$a)
正如 Perl 开发者所知,它有特殊的变量
$a
和$b
.Raku 将此概念概括为
$^foo
parameters, a convenient DRY 将位置参数添加到闭包的方式,同时跳过通常的仪式,在仪式中必须指定两次名称(一次声明它,另一次使用它)。它们的一个不寻常的方面是,它们的形式参数位置由它们的名称决定,最初可能看起来很疯狂,但实际上非常符合人体工程学。因此,给定
$^foo
和$^bar
,后者将绑定到 第一个 位置参数,$^foo
到第二个
-
Missing comma after block argument to sort ------> { ... }⏏ keys
我在指定的地方插入了一个逗号。
-
Calling split(Str) will never work with signature of the proto ($, $, |) ------> my ($f1, $f2) = ⏏split(' '
一些 Perl 例程隐含地假定使用
$_
。没有 syntactic 方法可以知道任何给定的例程是否隐含地使用它。您只需要阅读每个例程的定义。 Raku 放弃了那个。因此 Rakudo 得出结论,
split
例程缺少要拆分的字符串。(“原型签名($,$,|)”中的
|
只是表示“可以可选传递的其他参数”,所以你可以忽略它。$, $
表示需要 两个 个参数,所以我们缺少一个。)对例程定义的快速检查显示
split
的sub
版本要求将字符串拆分为其 second 位置参数。因此我切换到split(' ', $_)
.
代码有效。 \o/
值得注意的是,所有 实际错误消息都以
===SORRY!=== Error while compiling
开头。这意味着他们甚至都在程序 运行 之前就被抓住了,这很好。
你已经得到了很好的答案,但我想借此机会向你介绍一些其他标准的 Raku 工具和习惯用法,这些工具和习语对我来说似乎很适合你的问题。
对于我的两个解决方案:
我的
%totals
变量以结构化数据形式存储键,而不仅仅是字符串键。假定的基本原理是简化排序和表示。 (但这实际上是为了向您展示另一种方式。确保将月份和日期数字连接为两个两位数字以确保正确排序当然是微不足道的。)我使用两种不同的键类型来显示这个主题的变化。我通过构建将名称映射到数字的哈希来处理 to/from 月份名称的转换。我用
.pairs
或.antipairs
方法声明一个,然后应用反向转换到另一个方向。我在第一个解决方案中以一种方式执行此操作,在第二个解决方案中以另一种方式执行此操作。我在一个解决方案中将jan
的数字设置为0
,在另一个解决方案中将1
的数字设置为
短小甜美,靠Pair
s
声明%foo
变量时,如果不指定其键类型,则默认为Str
。但是在这段代码中,%totals
中每个Pair
的key本身就是一个Pair
:
my %totals{Pair}; # Keys of the `Pair`s in `%totals` are themselves `Pair`s
my %months = <jan feb mar apr may jun jul aug sep oct nov dec> .pairs; # 0 => jan
for lines.grep(/redis/ & /Partial/)».words {
++%totals{ %months.antipairs.hash{ lc .[0] }.Int => .[1].Int }
}
for %totals .sort {
printf "%3s %2d : %-d\n", %months{.key.key}, .key.value, .value
}
如果未指定排序闭包,Raku 的 sort
例程在应用于散列时会通过使用 cmp
比较它们的键来对其条目进行排序。此外,对于普通哈希,比较两个键意味着比较两个字符串。
如果这些字符串是每个日期的月份和日期,每个都格式化为两位数,然后连接起来,那么这对您的情况来说会很好。或者,拆分和 schwartzian 也可以正常工作。 Raku 真的很擅长这些,但我更喜欢用不同的方式来回答这个问题,所以默认的排序是正确的。
对于第一个解决方案,我选择 Pair
s 作为密钥类型。当 cmp
比较 Pair
s 时,它首先按键排序,然后按其中的值排序。键和值都被强制为 Int
s,因此上面的代码正确地按月排序,然后是其中的天数。
更多结构,使用Date
s
此版本增加了结构和更花哨的打字。它将 %totals
散列的等效项(重命名为 %.data
)包装到包含一些实用程序的外部对象中,并使内部键对象成为 Date
而不是 Pair
:
role Totals {
my %months = <jan feb mar apr may jun jul aug sep oct nov dec> .antipairs «+» 1; # jan => 1
method month-name (Int $num --> Str) { %months.antipairs.hash{$num} }
method month-num (Str $name --> Int) { %months{lc $name} }
has %.data{Date} handles <sort>;
}
my $totals = Totals.new;
for lines.grep(/redis/ & /Partial/)».words {
++$totals.data{ Date.new: :year(2000), :month(Totals.month-num: .[0]), :day(.[1]) }
}
for $totals .sort {
printf "%3s %2d : %-d\n", Totals.month-name(.key.month), .key.day, .value
}
在第一个解决方案中,sort
做了正确的事情,因为它正在比较 Pair
s,而 cmp
反过来做了正确的事情,因为我是如何设置这些对的向上。
在这个解决方案中 sort
/cmp
做正确的事情而不需要将字符串值强制为 Int
s,因为总计条目是 Date
s 并且它们按普通日期比较规则比较