Raku CookBook(inspired by Python cookbook)
数据结构和算法
解压序列赋值给多个变量
问题
现在有一个包含 N 个元素的元组或者是序列,怎样将它里面的值解压后同时赋值 给 N 个变量?
解决方案
任何的序列 (或者是可迭代对象) 可以通过一个简单的赋值语句解压并赋值给多个 变量。唯一的前提就是变量的数量必须跟序列元素的数量是一样的。
代码示例:
> my @p = (4,5);
[4 5]
> my ($x, $y) = @p;
(4 5)
> $x
4
> $y
5
> my @data = ('ACME', 50, 91.1, (2012, 12, 21) );
[ACME 50 91.1 (2012 12 21)]
> my ($name, $shares, $price, ($year, $mon, $day)) = ('ACME', 50, 91.1, |(2012, 12, 21) );
(ACME 50 91.1 2012 12 21)
> $name
ACME
> $mon
12
> $day
21
# 或者使用签名字面值和绑定(REPL 模式下报错, 需要在脚本中运行!)
> my ($name, $shares, $price, $year, $mon, $day); # a signature literal
> :($name, $shares, $price, ($year, $mon, $day)) := ('ACME', 50, 91.1, (2012, 12, 21) ); # binding operation
如果变量个数和序列元素的个数不匹配,会有变量的值为 Any
。
代码示例:
> my ($x,$y,$z) = @p;
(4 5 (Any))
字符串如果 unpack 则需要先 comb
:
my $s = 'Hello';
my ($a, $b, $c, $d, $e) =$s.comb;
> $a
H
> $b
e
> $e
o
有时候,你可能只想解压一部分,丢弃其他的值。对于这种情况 Raku 并没有提 供特殊的语法。但是你可以使用任意变量名去占位,到时候丢掉这些变量就行了。
代码示例:
my @data = ('ACME', 50, 91.1, |(2012, 12, 21));
my ($, $shares, $price, $) = @data;
> $shares
50
> $price
91.1
解压可迭代对象赋值给多个变量
如果一个可迭代对象的元素个数超过变量个数时,会抛出一个 Error。那么 怎样才能从这个可迭代对象中解压出 N 个元素出来?
解决方案
Raku 使用星号来解决这个问题。比如,你在学习一门课程,在学期 末的时候,你想统计下家庭作业的平均成绩,但是排除掉第一个和最后一个分数。如 果只有四个分数,你可能就直接去简单的手动赋值,但如果有 24 个呢?这时候星号表 达式就派上用场了:
sub drop_first_last(@grades) {
my ($first, *middle, $last) = @grades;
return avg(@middle);
}
另外一种情况, 假设你现在有一些用户的记录列表, 每条记录包含一个名字、邮 件,接着就是不确定数量的电话号码。你可以像下面这样分解这些记录:
> my @record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')
[Dave dave@example.com 773-555-1212 847-555-1212]
> my ($name, $email, @phone_numbers ) = @record
(Dave dave@example.com [773-555-1212 847-555-1212])
> say @phone_numbers
[773-555-1212 847-555-1212]
保留最后 N 个元素
问题
实现 python 中的 deque
查找最大或最小的 N 个元素
问题
怎样从一个集合中获得最大或者最小的 N 个元素列表?
解决方案
my @nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2];
say @nums.sort.head(3); # 最小的前三个元素
say @nums.sort.tail(3); # 最大的前三个元素
sort 还可以对更复杂的数据结构进行排序:
my @portfolio = [
{'name'=> 'IBM', 'shares'=> 100, 'price'=> 91.1},
{'name'=> 'AAPL', 'shares'=> 50, 'price'=> 543.22},
{'name'=> 'FB', 'shares'=> 200, 'price'=> 21.09},
{'name'=> 'HPQ', 'shares'=> 35, 'price'=> 31.75},
{'name'=> 'YHOO', 'shares'=> 45, 'price'=> 16.35},
{'name'=> 'ACME', 'shares'=> 75, 'price'=> 115.65}
];
say @portfolio.sort(*.{'price'}).head(3);
say @portfolio.sort(-*.{'price'}).head(3);
# ({name => YHOO, price => 16.35, shares => 45} {name => FB, price => 21.09, shares => 200} {name => HPQ, price => 31.75, shares => 35})
# ({name => AAPL, price => 543.22, shares => 50} {name => ACME, price => 115.65, shares => 75} {name => IBM, price => 91.1, shares => 100})
序列中出现次数最多的元素
问题
怎样找出一个序列中出现次数最多的元素呢?
解决方案
使用 Bag:
my @words = [
'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
'my', 'eyes', "you're", 'under'
];
say @words.Bag.sort(-*.value).head(3);
Bag 还可以进行集合操作:
my @more_words = [
'look', 'into', 'my', 'eyes',
'the', 'eyes', 'not', 'around', 'the',
'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
'my', 'eyes', "you're", 'under'
];
say @words.Bag ⊎ @more_words.Bag;
# Bag(around(4), don't(2), eyes(13), into(5), look(7), my(5), not(2), the(8), under(2), you're(2))
say @words.Bag (-) @more_words.Bag;
# Bag(eyes(3), into, look, my, the(2))
通过某个关键字排序一个字典列表
问题
你有一个字典列表,你想根据某个或某几个字典字段来排序这个列表。
解决方案
my @rows = [
{'fname' => 'Brian', 'lname' => 'Jones', 'uid' => 1003},
{'fname' => 'David', 'lname' => 'Beazley', 'uid' => 1002},
{'fname' => 'John', 'lname' => 'Cleese', 'uid' => 1001},
{'fname' => 'Big', 'lname' => 'Jones', 'uid' => 1004}
];
# sort by fname
.say for @rows.sort(*.{'fname'});
# sort by uid
.say for @rows.sort: *.{'uid'};
代码的输出如下:
{fname => Big, lname => Jones, uid => 1004}
{fname => Brian, lname => Jones, uid => 1003}
{fname => David, lname => Beazley, uid => 1002}
{fname => John, lname => Cleese, uid => 1001}
{fname => John, lname => Cleese, uid => 1001}
{fname => David, lname => Beazley, uid => 1002}
{fname => Brian, lname => Jones, uid => 1003}
{fname => Big, lname => Jones, uid => 1004}
通过某个字段将记录分组
问题
你有一个字典或者实例的序列,然后你想根据某个特定的字段比如 date 来分组迭代访问。
解决方案
my @rows = [
{'address' => '5412 N CLARK', 'date' => '07/01/2012'},
{'address' => '5148 N CLARK', 'date' => '07/04/2012'},
{'address' => '5800 E 58TH', 'date' => '07/02/2012'},
{'address' => '2122 N CLARK', 'date' => '07/03/2012'},
{'address' => '5645 N RAVENSWOOD', 'date' => '07/02/2012'},
{'address' => '1060 W ADDISON', 'date' => '07/02/2012'},
{'address' => '4801 N BROADWAY', 'date' => '07/01/2012'},
{'address' => '1039 W GRANVILLE', 'date' => '07/04/2012'}
];
.say for @rows.classify: *.{'date'};
过滤序列元素
问题
你有一个数据序列,想利用一些规则从中提取出需要的值或者是缩短序列
解决方案
字符串对齐
问题
你想通过某种对齐方式来格式化字符串
my $text = 'Hello World'
Hello World
> $text.indent(4)
Hello World
> " indented by 2 spaces\n indented even more".indent(*)
indented by 2 spaces
indented even more
合并拼接字符串
问题
你想将几个小的字符串合并为一个大的字符串
解决方案
my @parts = ['Is', 'Chicago', 'Not', 'Chicago?'];
[Is Chicago Not Chicago?]
> @parts.join(' ');
Is Chicago Not Chicago?
基本的日期与时间转换
> my $today = Date.today
2018-05-04
# 10 天后
> $today.later(days => 10)
2018-05-14
# 两个月后
> $today.later(month => 2)
2018-07-04
# 一天后
> $today + 1
2018-05-05
# 两个日期之间相差的天数
> my $d = Date.new(2018,5,12)
> $d - $today
8
计算上一个周五的日期
问题
你需要查找星期中上个星期五的日期。
解决方案
# error
(Date.today.later(days => -7) ... Date.today).grep: *.day-of-week==5
# error, last friday
(Date.today.later(days => -8) ... Date.today).grep(*.day-of-week==5)[0]
# error
Date.today, *.earlier(:1day) ... ( *.day-of-week==5 && *.week[1]+1==Date.today.week[1] )
# another way
@ = Date.today -1, *.pred ... *.day-of-week == 5 andthen .tail
# 但是上面的例子是错误的, 需要使用 Junction 来确定 endpoint
(Date.today, *.earlier(:1day) ... all( *.day-of-week==5, *.week[1]+1==Date.today.week[1] )).tail
# or
(Date.today-1, *.pred ... all( *.day-of-week==5, *.week[1]+1==Date.today.week[1] )).tail
# or
(Date.today-1, *.pred ... -> $d { $d.day-of-week==5 && $d.week[1]+1==Date.today.week[1] }).tail
同理, 可以计算出下个周五:
(Date.today, *.later(:1day) ... all( *.day-of-week==5, *.week[1]==Date.today.week[1]+1 )).tail
# or
(Date.today+1, *.succ ... all( *.day-of-week==5, *.week[1]==Date.today.week[1]+1 )).tail
反转散列
my @weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
@weekdays.kv.reverse.hash.{'Thursday'} # 3
# antipairs 翻转键值对儿
@weekdays.antipairs.hash.{'Sunday'}
# 使用 :k 副词
@weekdays.first('Thursday', :k) # 3
格式化日期
Date.new('2018-05-04', formatter => sub ($self) {sprintf "%04d/%02d/%02d", .year, .month, .day given $self})
2018/05/04
> Date.new('2018-05-04', formatter => {sprintf "%04d/%02d/%02d", .year, .month, .day })
2018/05/04
如何避免语法重复和样板
%tweets<statuses>[0]<metadata><iso_language_code>.say;
%tweets<statuses>[0]<created_at>.say;
可以被写为:
given %tweets<statuses>[0] {
.<metadata><iso_language_code>.say;
.<created_at>.say;
}
这使用了主题变量 $_
。简而言之,对于简短的循环也可以使用主题变量,如下所示:
for @(%tweets<statuses>) {
.<created_at>.say;
}
解构
subset Seconds of Numeric;
my regex number { \d+ [ '.' \d+ ]? } # float
my regex suffix { <:alpha> } # 只匹配字母
my %unit-multipliers = 'd' => 60*60*24, 'h' => 60*60, 'm' => 60, 's' => 1; # 每天, 每小时, 每分钟, 每秒所对应的秒数
# @timicles => [0.5m 10s]
sub MAIN(*@timicles where .all ~~ /<number> <[dhms]>/) {
my Seconds $to-wait = @timicles»\
.match(/<number> <suffix>+/)».hash\ # the +-quatifier is a workaround
.map(-> % ( Rat(Any) :$number, Str(Any) :$suffix ) { %unit-multipliers{$suffix} * $number })\
.sum;
say $to-wait ~ "s";
}
# Usage: raku program_file.pl6 1d 2h 3m 5s
map 里面使用了 Pointy-block -> {}
,
-> % (:$number, :suffix) { }
%
是一个匿名散列。
在正则表达式中使用数组
subset Seconds of Numeric;
# Junction 作为散列的键, 可以让多个键对应同一个值, Also, any @keys => value is possible
my %unit-multipliers = 'd'|'day' => 60*60*24, 'h'|'hour' => 60*60, 'm'|'min' => 60, 's'|'sec' => 1;
my @units = %unit-multipliers.keys;
my regex number { \d+ [ '.' \d+ ]? }
my regex suffix { @units }
# @timicles => [0.5m 10s 0.5min, 10sec 1day 1d]
sub MAIN(*@timicles where .all ~~ /<number> @units $/) {
my Seconds $to-wait = @timicles»\
.match(/<number> <suffix> $/)».hash\s
.map(-> % ( :$number, :$suffix ) { %unit-multipliers{$suffix} * $number })\
.sum;
say $to-wait ~ "s";
}
多个字符在字符串中的索引
有一个字符串 banana, 要查看字符 a 和 字符 b 在 banana 中的索引:
> "banana".comb.grep: 'a' | 'b',:k
(0 1 3 5)
# or
gather for 'banana'.comb.antipairs {.value.take if .key ∈ ['a','b'] }
# or
gather 'banana'.comb.antipairs».&{.value.take if .key ∈ ['a','b'] }
# or
> 'banana'.comb.antipairs>>.&{.{.key (&) ['a','b']}}
(0 1 Nil 3 Nil 5)