第五章 Perl6 列表与数组
列表字面量
包裹在()
内并使用逗号分隔的项称为列表项。
其实并不需要使用小括号包裹列表项,这些列表项会自动声明自己的优先级。
译注:
("a", "b", 0); # 等同于 "a", "b", 0;
列表就是标量变量的有序集合,诸如:
tutorial/arrays/list_literals.p6
#!/usr/bin/env perl6
use v6;
(1, 5.2, "apple"); # 3个值
(1,2,3,4,5,6,7,8,9,10); # 非常棒的写法,但是如果我们想要偷懒的话应该这样写:
(1..10); # 等同于 (1,2,3,4,5,6,7,8,9,10)
(1..Inf); # 表示包含所有正整数的列表
(1..*); # 这个也一样
("apple", "banana", "peach", "blueberry"); # 等同于
<apple banana peach blueberry>; # 引用字符串
my ($x, $y, $z); # 同样能够使用标量变量作为列表项
译注:
my ($x, $y, $z) = 2, 3, 4 say $x # 2 say $y # 3 say $z # 4
注意
($x, $y, $z)
中的括号不可以省略。
事实上大多数情况下都不需要使用小括号。
列表赋值
my ($x, $y, $z);
($x, $y, $z) = f(); # 如果 f() 返回 (2, 3, 7),等同于 $x=2; $y=3; $z=7;
($x, $y, $z) = f(); # 如果 f() 返回 (2, 3, 7, 9),9会被忽略
($x, $y, $z) = f(); # 如果 f() 返回 (2, 3),$z将是 undef
一个常见的面试题:如何交换$x,$y两个变量的值?
译注:
my $x = 233 my $y = 666 ($x, $y) = ($y, $x) say $x # 666 say $y # 233
交换两个变量
tutorial/arrays/lists.p6
#!/usr/bin/env perl6
use v6;
say "Type in two values:";
my $a = $*IN.get;
my $b = $*IN.get;
($a, $b) = ($b, $a);
say $a;
say $b;
使用for
循环遍历列表元素
tutorial/arrays/arrays.p6
#!/usr/bin/env perl6
use v6;
for "Foo", "Bar", "Baz" -> $name {
say $name;
}
say "---";
for 1..5 -> $i {
say $i;
}
say "---";
for 1..Inf -> $i {
say $i;
last if $i > 3;
}
say "---";
for 1..* -> $i {
say $i;
last if $i > 3;
}
建立数组并遍历之
数组声明使用@
开头,后面跟以数组名。你可以给数组赋一列值。
数组也可以被嵌入到字符串中,此时数组标识符需要使用{}
来包裹,数组各项之间将会由空格分隔。
for
循环能够遍历数组中的所有元素。
可以看出数组周围的小括号是可选的。
tutorial/arrays/list_colors_array.p6
#!/usr/bin/env perl6
use v6;
my @colors = "Blue", "Yellow", "Brown", "White";
say @colors;
say "--------------------------------";
say "@colors"; # 直接输出 "@colors"!
say "--------------------------------";
say "{@colors}"; # 输出数组中各项,使用空格分隔
say "--------------------------------";
say "@colors[]"; # 输出数组中各项,使用空格分隔
say "--------------------------------";
for @colors -> $color {
say $color;
}
数组元素(创建目录)
tutorial/arrays/color_menu.p6
#!/usr/bin/env perl6
use v6;
my @colors = <Blue Yellow Brown White>;
for 1..@colors.elems -> $i {
say "$i) @colors[$i-1]";
}
my $input = prompt("Please select a number: ");
say "You selected @colors[$input-1]";
数组赋值
tutorial/arrays/array_assignment.p6
#!/usr/bin/env perl6
use v6;
my $owner = "Moose";
my @tenants = "Foo", "Bar";
my @people = ($owner, 'Baz', @tenants); # 数组会被铺平:
say "{@people}"; # Moose Baz Foo Bar
my ($x, @y) = (1, 2, 3, 4);
say $x; # $x = 1
say "{@y}"; # @y = (2, 3, 4)
译注:
@people
数组事实上并不会被铺平,第三项依然会作为数组存在,只是作为字符串输出时会被铺平。@people.say # [Moose Baz [Foo Bar]]
命令行选项
@*ARGS
是一个由语言本身维护的数组,存有来自命令行的值。
此数组不包含程序名,只包含程序名后的参数。
前文中已经提到过,程序名以及其相对路径存放在$*PROGRAM-NAME
变量中。
tutorial/arrays/color_menu_option.p6
#!/usr/bin/env perl6
use v6;
my $color = @*ARGS[0];
if not $color {
my @colors = <Blue Yellow Brown White>;
for 1 .. @colors.elems -> $i {
say "$i) @colors[$i-1]";
}
my $input = prompt "Please select a number: ";
$color = @colors[$input-1];
}
say "You selected $color";
处理CSV文件
tutorial/arrays/sample_csv_file.csv
Foo,Bar,10,home
Orgo,Morgo,7,away
Big,Shrek,100,US
Small,Fiona,9,tower
tutorial/arrays/process_csv_file.p6
#!/usr/bin/env perl6
use v6;
my $file = 'examples/arrays/sample_csv_file.csv';
if defined @*ARGS[0] {
$file = @*ARGS[0];
}
my $sum = 0;
my $data = open $file;
for $data.lines -> $line {
my @columns = split ",", $line;
$sum += @columns[2];
}
say $sum;
联结
tutorial/arrays/join.p6
#!/usr/bin/env perl6
use v6;
my @fields = <Foo Bar foo@bar.com>;
my $line = join ";", @fields;
say $line; # Foo;Bar;foo@bar.com
unique
函数
tutorial/arrays/unique.p6
#!/usr/bin/env perl6
use v6;
my @duplicates = 1, 1, 2, 5, 1, 4, 3, 2, 1;
say @duplicates.perl;
# [1, 1, 2, 5, 1, 4, 3, 2, 1]
my @unique = unique @duplicates;
say @unique.perl;
# [1, 2, 5, 4, 3]
my @chars = <b c a d b a a a b>;
say @chars.perl;
# ["b", "c", "a", "d", "b", "a", "a", "a", "b"]
my @singles = unique @chars;
say @singles.perl;
# ["b", "c", "a", "d"]
单值遍历
Perl6中遍历数组元素的标准做法是使用for
语句,以下是一个简单示例:
tutorial/arrays/loop_over_array.p6
#!/usr/bin/env perl6
use v6;
my @fellows = <Foo Bar Baz>;
for @fellows -> $name {
say $name;
}
此段代码将会依次打印数组中的三个项。
示例代码解析:
@fellows
是一个含有三个项的数组。
循环变量($name
)在循环体中会自动声明,因此无需使用my
关键字,并且其作用域仅限于循环体而非全局范围。
多值遍历
你可以每次访问数组中多个元素:
例如,我们从足球网站http://soccernet.espn.go.com/提取出西甲的比赛结果,四项数据构成一组:
home team, score of home team, score of guest team, guest team
可以使用带有四个标量变量的for
循坏来访问所有的列表项:
tutorial/arrays/looping_over_many_elements.p6
#!/usr/bin/env perl6
use v6;
my @scores = <
Valencia 1 1 Recreativo_Huelva
Athletic_Bilbao 2 5 Real_Madrid
Malaga 2 2 Sevilla_FC
Sporting_Gijon 3 2 Deportivo_La_Coruna
Valladolid 1 0 Getafe
Real_Betis 0 0 Osasuna
Racing_Santander 5 0 Numancia
Espanyol 3 3 Mallorca
Atletico_Madrid 3 2 Villarreal
Almeria 0 2 Barcelona
>;
for @scores -> $home, $home_score, $guest_score, $guest {
say "$home $guest $home_score : $guest_score";
}
译注:
Valencia Recreativo_Huelva 1 : 1 Athletic_Bilbao Real_Madrid 2 : 5 Malaga Sevilla_FC 2 : 2 Sporting_Gijon Deportivo_La_Coruna 3 : 2 Valladolid Getafe 1 : 0 Real_Betis Osasuna 0 : 0 Racing_Santander Numancia 5 : 0 Espanyol Mallorca 3 : 3 Atletico_Madrid Villarreal 3 : 2 Almeria Barcelona 0 : 2
缺少列表项
你可能会问:如果在进行多值遍历的时候,遍历到最后一组但是列表中的项不够用了怎么办?
tutorial/arrays/missing_values.p6
#!/usr/bin/env perl6
use v6;
for (1, 2, 3, 4, 5) -> $x, $y {
say "$x $y";
}
say 'done';
此时循环体将会自动丢弃多余的项,即不处理值不足的最后一次循环,并抛出异常。
tutorial/arrays/missing_values.p6.output
1 2
3 4
Not enough positional parameters passed; got 1 but expected 2
in block <anon> at examples/arrays/missing_values.p6:4
为了防止异常,我们可以在循环体参数的后面加上问号来告诉循环第二个以及其随后的参数是可选的。
tutorial/arrays/missing_values_fixed.p6
#!/usr/bin/env perl6
use v6;
for (1, 2, 3, 4, 5) -> $x, $y? {
say "$x $y";
}
say 'done';
此程序将会产生下列输出:
1 2
3 4
Use of uninitialized value of type Mu in string context.
Methods .^name, .perl, .gist, or .say can be used to stringify it to something meaningful.
in block <anon> at examples/arrays/missing_values_fixed.p6:5
5
done
译注:
此写法仅限于循环变量是两个的情况,即若数组中的值不足也只可能是循环体参数表中最后一个参数不能被满足。下列情况将依旧抛出异常并中断程序:
for (1, 2, 3, 4, 5, 6, 7) -> $x, $y, $z? { say "$x $y $z"; } say 'done';
输出:
1 2 3 4 5 6 Too few positionals passed; expected 2 or 3 arguments but got 1 in block <unit> at 444.p6 line 1
当然你可以将循环参数表中除第一个参数以外的其他参数均设成可选的,并使用相应的判断来正确处理所有情况:
for @arr -> $a, $b?, $c?, $d? { say "---"; say $a; say $b if $b.defined; say $c if $c.defined; say $d if $d.defined; }
详情以及更多处理方法请仔细阅读问题How to be aware of missing values during multi-value iteration in Perl 6?。
同时遍历多个数组
在接下来的例子中我将呈现一个完全不同的场景。
如果你有两个(或者多个)数组并想将它们以某种方式组合在一起,该如何同时遍历这些数组?
tutorial/arrays/z.p6
#!/usr/bin/env perl6
use v6;
my @chars = <a b c>;
my @numbers = <1 2 3>;
for @chars Z @numbers -> ($letter, $number) {
say "$letter $number";
}
输出:
tutorial/arrays/z.p6.out
a 1
b 2
c 3
Z
即zip
zip
函数的中缀运算符版本Z
能够让你同时处理两个及以上的数组:
tutorial/arrays/z_on_more.p6
#!/usr/bin/env perl6
use v6;
my @operator = <+ - *>;
my @left = <1 2 3>;
my @right = <7 8 9>;
for @left Z @operator Z @right -> ($a, $o, $b) {
say "$a $o $b";
}
输出:
tutorial/arrays/z_on_more.p6.out
1 + 7
2 - 8
3 * 9
xx
字符串倍率器
tutorial/arrays/xx.p6
#!/usr/bin/env perl6
use v6;
my @moose = "moose" xx 3;
say "{@moose}"; # moose moose moose
排序
tutorial/arrays/sort.p6
#!/usr/bin/env perl6
use v6;
my @names = <foo bar moose bu>;
my @sorted_names = sort @names;
say @sorted_names.perl; # ["bar", "bu", "foo", "moose"]
my @numbers = 23, 17, 4;
my @sorted_numbers = sort @numbers;
say @sorted_numbers.perl; # [4, 17, 23]
my @sort_names_by_length = sort { $^a.chars <=> $^b.chars }, @names;
say @sort_names_by_length.perl; # ["bu", "foo", "bar", "moose"]
# 施瓦兹变换 (Schwartzian transformation)
my @sort_1 = sort { $_.chars }, @names;
say @sort_1.perl; # ["bu", "foo", "bar", "moose"]
my @sort_2 = @names.sort({ $_.chars });
say @sort_2.perl; # ["bu", "foo", "bar", "moose"]
my @sort_3 = @names.sort: { $_.chars };
say @sort_3.perl; # ["bu", "foo", "bar", "moose"]
my @words = <moo foo bar moose bu>;
say @words.sort({ $^a.chars <=> $^b.chars}).perl; # ["bu", "moo", "foo", "bar", "moose"];
say @words.sort({ $^a.chars <=> $^b.chars or $^a cmp $^b}).perl; # ["bu", "bar", "foo", "moo", "moose"];
# TODO: 还应该实现:
# say @words.sort({ $^a.chars <=> $^b.chars }, {$^a cmp $^b}).perl; # ["bu", "bar", "foo", "moo", "moose"];
# say @words.sort({ $_.chars }, {$^a cmp $^b}).perl; # ["bu", "bar", "foo", "moo", "moose"];
译注:
施瓦兹变换
使用map
变换数组和列表
作为函数的map
tutorial/arrays/map_double_function.p6
use v6;
my @numbers = 2, 5, 7;
my @d = map { $_ * 2 }, @numbers;
say @d.perl; # [4, 10, 14]
my @x = map { $^number * 2 }, @numbers;
say @x.perl; # [4, 10, 14]
say @numbers.perl; # [2, 5, 7]
在第一个例子中,首先创建一个含有三个数字的数组,再对其使用map
函数创建一个原数组的副本。
变量$_
中含有数组中的当前元素。
map
函数实际上是一个循环,其在右操作数位置遍历数组中的每一项,每次将其赋给$_
变量,执行给定的代码块,并收集结果。收集结果后组成的数组将会赋给=
运算符左侧的数组。
在第二个例子中,我们使用了另一种自动生成的变量写法而非先前的默认变量写法$_
,这样我们就可以给变量起名字了。$
符号后的补字符号(caret)^
表示这是一个自动生成的变量。这样写的作用跟$_
其实是一样的,但是给临时变量起一个好名字会让你的代码更加可读。
perl
方法能够帮助你导出数组中的内容。
最终我们打印出原始的数组以表明它本身并没有被修改。
作为方法的map
map
方法具有和上述一样的功能。
tutorial/arrays/map_double_method.p6
use v6;
my @numbers = 2, 5, 7;
my @d = @numbers.map( -> $x {$x * 2 } );
say @d.perl; # [4, 10, 14]
my @b = @numbers.map( * * 2 );
say @b.perl; # [4, 10, 14]
say @numbers.perl; # [2, 5, 7]
在这个例子中我们创建了一个相同的数组并对其调用map
方法。
第一次调用中,我们使用了箭头运算符来创建一个内部变量,再在代码块中写上表达式,跟使用for
循环一样。
第二次的表达式中,第一个*
是任意(Whatever) 操作符,是当前变量的另一种占位符形式。本人并不推荐这种写法,因为它看上去非常不清晰。