Using Perl6 第二章:The Basics

2: The Basics

Table of Contents

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

Date: 2014-03-23T10:53+0800

Author: GRC(揚眉劍)

Org version 7.9.3f with Emacs version 24

Validate XHTML 1.0
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章