Raku CookBook

inspired by Python cookbook

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)

comments powered by Disqus