2: The Basics
Table of Contents
- 1 about
- 2 第二章:基础
1 about
github地址:https://github.com/gaorongchao/Perl6/tree/master/Using_perl6
如果发现任何错误和翻译不当的地方,请告知,非常感谢。
2 第二章:基础
Perl起源于一个致力于从文本文件中收集整理信息的编程语言。 现在Perl在文本处理方面仍然强大,Perl 5 是一个一般意义上的强力编程语言。 但是Perl 6更为杰出。
假设你举办了一个乒乓球联赛。 裁判告诉你比赛结果的格式:选手1 选手2 | 3:2,这意味着,选手1赢了3局,选手2赢了2局。 你需要一个脚本来,来统计选手赢了几场比赛,赢了多少局,并以此决定最终冠军的归属。
输入文件格式如下(储存在一个名为“scores”的文件中):
Beth Ana Charlie Dave Ana Dave | 3:0 Charlie Beth | 3:1 Ana Beth | 2:3 Dave Charlie | 3:0 Ana Charlie | 3:1 Beth Dave | 0:3
第一行是所有选手的名字,后面跟着的是每场比赛的结果。 这里有一个解决此问题的Perl 6 脚本:perl-1-1:
1: use v6; 2: 3: my $file = open 'scores'; 4: my @names = $file.get.words; 5: 6: my %matches; 7: my %sets; 8: 9: for $file.lines -> $line { 10: my ($pairing, $result) = $line.split(' | '); 11: my ($p1, $p2) = $pairing.words; 12: my ($r1, $r2) = $result.split(':'); 13: 14: %sets{$p1} += $r1; 15: %sets{$p2} += $r2; 16: 17: if $r1 > $r2 { 18: %matches{$p1}++; 19: } else { 20: %matches{$p2}++; 21: } 22: } 23: 24: my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse; 25: 26: for @sorted -> $n { 27: say "$n has won %matches{$n} matches and %sets{$n} sets"; 28: }
输出文件:
Ana has won 2 matches and 8 sets Dave has won 2 matches and 6 sets Charlie has won 1 matches and 4 sets Beth has won 1 matches and 4 sets
每一个Perl6程序都需要以 “use v6”; 开始。这一行告诉编译器Perl代码的版本。 如果,你不小心用Perl5来运行这个程序,你将得到错误信息。
一个Perl6程序包含0行或者更多的语句。每一个语句以分号结尾,或者以大括号结尾:
1: my $file = open 'scores';
"my"声明一个词法变量。词法变量只在当前代码块可用,从声明开始到代码块的结束。 如果一个代码块始终没有闭合,那么该变量可以在剩下的所有地方使用。代码块的定义是: 一段在花括号\{\}之间的代码。
一个变量名称以“魔符”开始,魔符是指那些非字母,非数字的符号,比如:$,@,% 或者&- 或者有时出现的双冒号“::”也是。 魔符指明了变量的结构类型,比如:它应该被当作一个值,还是多个值,还是一个子程序,等等。 跟在“魔符”后面的是“标识符”,标识符可以包含字母,数字和下划线。在字母与字母之间, 你可以用短横线“-”或者撇号“'”。所以“isn't”和“double-click”都是合法的变量标识符。
魔符“$”表明变量是一个数值变量,指明这个变量只存储了一个值。
内建函数“open”打开了一个名为“scores”的文件,然后返回一个文件句柄, 文件句柄是一个代表该文件的对象。 赋值符号“=”,把文件句柄赋值给左边的变量,这也就是意味着变量$file现在存储着文件句柄。
“scores”是字符串文本(string literal)。字符串(string)是一段纯文本。 字符串文本(string literal)是直接出现在程序中的字符串。 在这一行中,它是提供给open函数的参数。
1: my @names = $file.get.words;
上面语句的右侧调用了一种“方法”(一个命名的一系列行为的集合体)获取存储在$file变量中的句柄。 被命名为get的这一方法从文件中读取并返回一行,去掉末尾的换行符。word同样是一个方法,调用从 get中返回的一行文本。word这一方法,分解它的invocant(它要操作的字符串)成一系列的单词。 这里单词的意思是不包含空格的字符串。它把字符串“Beth Ana Charlie Dave”分解成多个字符串 “Beth”,“Ana”,“Charlie”,“Dave”。
最后,这一字符串列表存储到数组@names中。魔符@表明所声明的变量是一个数组。数组存储的是有序列表。
1: my %matches; 2: my %sets;
这两行代码声明了两个哈希。魔符“%”把变量标记为哈希。哈希又称为散列:是无序的键-值对的集合。 在其他语言中又称为“哈希表”,“字典”或者“(map)图” 你可以通过“键 $key”利用“%hash{$key}”来查询在哈希表中的值。
在上述计分程序中,%matches记录了每一位运动员赢的次数。%sets记录了每一位运动员赢的局数。
魔符指出了默认访问变量的方法。数组变量@是通过位置来访问, 哈希变量%是通过“键”来访问。 数值变量$表示一个大箩筐,这里可以盛任何东西,也可以用任何方式来访问。 一个数值变量可以包含一个复杂对象(compound object),比如:数组或者哈希; 魔符$表明它应该被当作一个单独的值,尽管有可能它包含众多的值(像一个数组或者哈希)。
1: for $file.lines ->$line{ 2: ... 3: }
“for”产生了一个由花括号界定运行范围的循环,循环会遍历列表中的每一个值。 $file.lines 从scores文件中读取了很多行,除掉前面$file.get读取的那一行以外,到最后一行。 循环依次将每一行的值赋予$line变量。
第一次迭代$line将包含字符串 “Ana Dave | 3:0”;第二次迭代$line将包含字符串“Charlie Beth | 3:1”,等等。
1: my ($pairing,$result) = $line.split('|');
“my”可以同时声明多个变量。在赋值符号(=)的右侧调用了一个名为“split”的方法,它把“|”当作参数。
split把他作用的内容以|为分割点,分割成一系列的字符串。所以如果你用“|”作为粘合符号,把这些字符串粘合。 那么你将得到原始字符串。
$pairing 得到返回列表的第一个元素,$result 得到第二个。
当处理完第一行以后,$pairing 包含“Ana Dave”这个字符串,$result包含“3:0”。
下面两行是同样的模式:
1: my ($p1,$p2) = $pairing.words; 2: my ($r1,$r2) = $result.split(':');
第一行代码提取并保存两位运动员的姓名到$p1和$p2这两个变量中。
第二行提取每一位运动员的比赛结果,并且分别保存到$r1和$r2中。
上面程序运行以后,下面列出每一个变量包含的值:
Table2.1:Contents of Variables
1: Variable Contents 2: $line 'Ana Dave | 3:0' 3: $pairing 'Ana Dave' 4: $result '3:0' 5: $p1 'Ana' 6: $p2 'Dave' 7: $r1 '3' 8: $r2 '0'
程序接下来统计了每一位运动员赢的局数:
1: %sets{$p1} += $r1; 2: %sets{$p2} += $r2;
这是下面的简写:
1: %sets{$p1} = %sets{$p1}+$r1; 2: %sets{$p2} = %sets{$p2}+$r2;
+= $r1 的含义是:把左侧的变量加上$r1. 在第一次迭代的时候%sets{$p1}还没有设置类型, 所以默认为一个称为“Any”的特殊值。 加法和递增操作符作为一个数字操作符,把Any当作一个值为0的数字。 所以这个字符串自动转换为数字。
在这两行代码执行以前,%sets 是空的。 在hash中添加一个完全不存在的元素,将会使这个元素立马在哈希中存在。 并且赋予初始值为0.(这被称为autovivification)。 当这两行第一次运行以后,%sets包含“ 'Ana'=> 3,'Dave'=>0”。 (胖箭头=>把键值对分开)
1: if $r1>$r2{ 2: %matches{$p1}++; 3: } else { 4: %matches{$p2}++; 5: }
如果$r1 的值大于$r2的值,那么%matches{$p1}增加1。 如果$r1 的值小于$r2的值,那么%matches{$p2}增加1。 和前面的+=的例子一样,如果哈希中没有存在这个值, 那么这个哈希值将会因为自增符号而自动生成。
$thing++ 是$thing +=1的缩写。 需要注意的一点是,$thing++这个表达式返回的$thing的值是没有增加前的值。 而不是增加以后的。在其他很多的编程语言中你可以把++前置。这样得到的增加 以后的返回值。my $x=1; say ++$x 输出2。
1: my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;
这一行代码包含三个独立的过程。首先调用数组的sort方法。 但是默认的排序方法是按照内容排序的。为了按照赢得多少的顺序输出运动员的姓名, 我们的程序必须按照运动员的成绩来排序,而不是用他们的名字。 sort方法的参数一个代码块,用来把数组元素(运动员的名字)转化为用来排序的数据。 数组元素是通过“$_”变量来传递的。
你在前面已经看到过代码块:不论是“for loop-> {…}”还是在代码块中的if语句。 代码块是:一个独立的带有 signature(the ->$line 的部分)Perl6代码。 更多内容请参照sec::signatures。
按照运动员成绩排序的最简单的方法是@names.sort({ %matches{$_} }), 这是按照运动员赢的次数决定的。但是问题是,Ana 和Dave都赢了2次。 最简单的排序方式并没有考虑到每个人赢的局数,而这个正是决定谁赢的循环赛的第二个评判标准。
如果两个数组元素拥有相同的值, sort按照发现他们的先后顺序放置他们。 计算机科学家们称之为:稳定排序。 这个程序充分利用了Perl6排序的相关特性来利用两次排序来实现目标: 第一次按照赢取局数的多少排序(也就是次要的冠军评判标准),然后按照赢取的次数来排序。
在第一次排序以后,运动员的名字顺序是这样的:Beth Charlie Dave Ana。 在第二次排序以后,顺序依然一样。因为没有人赢了更少的次数,但是赢了更多的局数。 这种情况是很正常的,特别是在大型的循环赛中。
sort是按照升序来排序,也就是从小到大排序。 但这与我们的期望是相反的。所以,我们的程序在第二次排序以后,又调用了.reverse方法。 最后把结果存入到@sorted数组中。
1: for @sorted -> $n { 2: say "$n has won %matches{$n} matches and %sets{$n} sets"; 3: }
为了输出运动员的名字和他们的成绩,我们对@sorted数组进行循环。 依次将运动员名字赋值给$n。我们可以这样来读代码“对数组sorted中的每一个元素, 赋值给$n,然后执行下面的代码块”。 然后用say 进行标准输出(也就是输出到屏幕)。 然后后面自动加上换行符。 如果你不想在最后加上换行符,那就用print函数。
当你运行本程序的时候,你会发现,say并不会逐字的输出所有内容。 在$n的位置上,它将会打印变量$n的内容:也就是存储在$n中的运动员的名字。 这种自动替换被称为“interpolation(变量内插)” 这种变量内插只会发生在双引号内,而不会发生在单引号内。
1: my $names = 'things'; 2: say 'Do not call me $name'; # Do not call me $names 3: say "Do not call me $name"; # Do not call me things
在Perl6中,双引号不仅能够内插魔符$的变量,同时也可以内插在花括号内的代码块。 因为任意的Perl代码都可以通过花括号出现,所以可以通过把数组和哈希放在花括号内 实现内插的效果。
数组放在花括号内插以后,两个元素之间会插入一个空格。 哈希放在花括号内插以后,以多行的形式显示,每一行包括一个键,然后跟着一个制表符。 然后紧跟这键对应的值,最后加上换行符。
1: say "Math: {1+2}"; # Match: 3 2: my @people = <Luke Mattew Mark>; 3: say "The synoptics are: {@people}"; # The synoptics are :Luck Matthew Mark 4: say "{%sets}"; # 接前面网球循环赛 5: # Charlie 4 6: # Dave 6 7: # Ana 8 8: # Beth 4
当数组和哈希变量直接出现在双引号之间的时候(并没有在花括号之间),那么只有在它们的名字 后面紧跟方括号的时候才会变量内插。当然你也可以在变量名和postcircumfix之间调用一种 方法来实现变量内插。
1: my @flavours = <vanilla peach>; 2: say "we have @flavours"; # we have @flavours 3: say "we have @flavours[0]" # we have vanilla 4: # so-called "Zen slice" 5: say "we have @flavours[]"; # we have Vanilla peach 6: 7: # method calls ending in postcircumfix 8: say "we have @flavours.sort()"; # we have peach vanilla 9: 10: # chained method calls: 11: say "we have @flavours.sort.join(',')"; # we have peach ,vanilla