(十三)智慧匹配與given-when結構、高階Perl技巧(~~、陣列/雜湊切片、eval/grep/map運用等)
技術標籤:Perl學習
文章目錄
一、智慧匹配與given-when結構
1.1、智慧匹配
1.1.1、智慧匹配操作符(~~)
智慧匹配操作是從Perl 5.010版本開始出現的,智慧匹配操作符(~~
- 如果兩邊運算元看起來都像數字,就按數值來比較大小;
- 如果看起來像字串,就按字串的方式進行比較;
- 如果某一端運算元是正則表示式,就當模式匹配來執行;
use 5.010001; #至少是5.10.1版
#例(1)
my $name = "You and Fred and me";
print "I found Fred in the name!\n" if $name =~ /Fred/;
##################################################智慧匹配##############################################
print "I found Fred in the name!\n" if $name ~~ /Fred/; #操作符~~取代繫結操作符=~功能進行模式匹配
#例(2)
my %names = ( "one" => 1,
"two" => 2,
"three" => 3,
"four" => 4,
"five" => 5,
"six" => 6,
) ;
my $flag = 0;
foreach my $key( keys %names){
next unless $key =~ /four/; #當變數$key不為four,執行next進入下一次迴圈
$flag = $key;
last; #當變數$key為four, 跳過next,執行last結束迴圈
}
print "I found a key matching 'four'.\n" if $flag; #字串自動轉換為布林值
######################################智慧匹配——直接替代了foreach迴圈的過程#####################################
print "I found a key matching 'four'.\n" if %names ~~ /four/; #操作符~~取代繫結操作符=~
#例(3)
my @name1 = qw(one two three four five six);
my @name2 = qw(one two three four five six);
my $equal = 0;
foreach my $index (0..$#name1){ #$#name1__最大索引值
last unless $name1[$index] eq $name2[$index];
$equal++;
}
print "The arrays have the same elements.\n" if $equal == @name1; #標量上下文
######################################智慧匹配——直接替代了foreach迴圈的過程#####################################
print "The arrays have the same elements.\n" if @name1 ~~ @name2; #操作符~~取代大小判斷符號==
#例(4)
my @nums = qw(1 2 7 15 27); #字串陣列
my $flag = 0;
foreach my $num (@nums){
next unless $num == 27; #查詢數字27,不是就執行next
$flag++;
last; #結束迴圈
}
print "I found the number 27.\n" if $flag;
######################################智慧匹配——直接替代了foreach迴圈的過程#####################################
print "I found the number 27.\n" if @nums ~~ /27/; #操作符~~取代繫結操作符=~;27用表示式表示
編譯執行:
I found Fred in the name!
I found Fred in the name!
I found a key matching 'four'.
I found a key matching 'four'.
The arrays have the same elements.
The arrays have the same elements.
I found the number 27.
I found the number 27.
1.1.2、智慧匹配優先順序
部分智慧匹配操作符對不同運算元的處理:
示例 | 匹配型別 |
---|---|
%a ~~ %b | 雜湊的鍵是否一致 |
%a ~~ @b 或者 @a ~~ %b | %a中的至少一個鍵在列表@b中 |
%a ~~ /Fred/ 或者 /Fred/ ~~ %b | 至少有一個鍵匹配給定的模式 |
‘Fred’ ~~ %a | 是否存在$a{Fred} |
@a ~~ @b | 陣列是否相同 |
@a ~~ /Fred/ | 陣列@a中至少有一個元素匹配模式 |
$name ~~ undef $name | $name沒有定義 |
$name ~~ /Fred/ | 模式匹配 |
123 ~~ ‘123.0’ | 數值和"numish"型別(看起來像數字的字元換)的字串是否相等 |
‘Fred’ ~~ ‘Fred’ | 字串是否相等 |
123 ~~ 123 | 數值是否相等 |
說明:智慧匹配並非始終符合交換律,至於匹配結果則取決於它第一個拿到的資料型別(優先順序)。
1.2、given-when結構
given-when控制結構能夠根據given後面的引數執行某個條件對應的語句塊。對應於C語言中的switch語句的等效物。
1.2.1、given語句
下面的程式碼從命令列讀取第一個引數,@ARGV[0],然後依次走一遍when條件測試,看是否可以找到Fred。每個when語句都對應於不同的處理方式:
- given會將引數化名為 $_, 然後對每個when條件試用智慧匹配對 $_作測試,智慧匹配部分可省略(隱式寫法)
- 如果$_不滿足任意一個when條件,Perl就會執行default分支語句塊;
use 5.012; #引入say的語法運用與智慧匹配~~
my $name = @ARGV[0]; #從命令列輸入引數
if($name =~ /fred/i) { say "if: name has fred in it."}
elsif($name =~ /^Fred/){ say "if: name starts with Fred."}
elsif($name eq 'Fred') { say "if: name is Fred." } #如果只有一行命令,則可以省略分號
else { say "if: I donnot say a Fred."}
#######################given——when實現(1.顯式寫法)####################3#####
given($name){
my $_ = $name;
when( $_ ~~ /fred/i){ say "when: name has fred in it." }
when( $_ ~~ /^Fred/){ say "when: name starts with Fred."}
when( $_ ~~ 'Fred') { say "when: name is Fred."}
default { say "whne: I donnot say a Fred."}
}
########given——when實現(2.隱式寫法)省略了預設變數$_與智慧匹配~~##############
given($name){
when( /fred/i){ say "when: name has fred in it." }
when( /^Fred/){ say "when: name starts with Fred."}
when( 'Fred') { say "when: name is Fred."}
default { say "whne: I donnot say a Fred."}
}
- when語句塊後面的每一條分支語句都預設帶有break關鍵字,跳出given-when結構,break關鍵字不需要自己輸入;
- 如果在when語句塊後邊使用continue關鍵字,Perl會繼續執行之後的when語句。
use 5.012; #引入say的語法運用與智慧匹配~~
#################given——when實現(continue與break使用)################
given(@ARGV[0]){ #從命令列輸入
when( /fred/i){ say "when: name has fred in it." ; continue} #分支中可以新增break和continue關鍵字
when( /^Fred/){ say "when: name starts with Fred."; continue} #含義同C語言
when( 'Fred') { say "when: name is Fred."} #每一條分支預設break執行
default { say "whne: I donnot say a Fred."} #如果$_不滿足任意一個when條件,Perl就會執行default分支語句塊。
}
- 笨拙匹配(泛匹配):given-when語句中除了可以直接使用智慧匹配外,還可以直接使用預設變數 $_運用比較操作符或繫結操作符(不論型別)進行匹配;
use 5.012; #引入say的語法運用與智慧匹配~~
given($ARGV[1]){ #從命令列輸入
when( /^-?\d+\.\d+$/ ){ say "this is a number."; continue} #智慧匹配
when( $_ > 10 ) { say "This number is greater than 10."} #泛匹配(直接使用預設變數$_)
when( $_ < 10 ) { say "This is a less than 10."}
default { say "This number is 10."}
}
編譯執行:
C:\Users\zhais\Documents\Perl學習>perl given_when.pl 12.5
when: name is Fred.
This is a less than 10.
1.2.2、多條目的when匹配(省略given)
有時需要遍歷多個條目,但是given一次只能接受一個引數。可以將given放到foreach裡進行迴圈測試,但是這種方法過於囉嗦。
遍歷多個元素,可以直接省略given,讓foreach將當前正在遍歷的元素放入自己的預設變數 $_中。因為接下來要用智慧匹配,所以當前元素只能放在 $_中。
- foreach必須使用預設變數$_;
- 可以在foreach語句塊中新增其它說明語句;
use 5.012; #引入say的語法運用與智慧匹配~~
my @name = qw(Fred frederick Barney Alfred);
foreach(@name){ #使用預設變數$_
say "\nProcessing $_"; #given-when中間可以插入一些說明語句(優於if)
#given($_){ #given也可以省略了
when( /fred/i){ say "when: name has fred in it." ; continue} #分支中可以新增break和continue關鍵字
when( /^Fred/){ say "when: name starts with Fred."; continue} #含義同C語言
when( 'Fred') { say "when: name is Fred."} #每一條分支預設break執行
say "Moving on to default...";
default { say "whne: I donnot say a Fred."}
#}
}
編譯執行:
C:\Users\zhais\Documents\Perl學習>perl given_when.pl
Processing Fred
when: name has fred in it.
when: name starts with Fred.
when: name is Fred.
Processing frederick
when: name has fred in it.
Moving on to default...
whne: I donnot say a Fred.
Processing Barney
Moving on to default...
whne: I donnot say a Fred.
Processing Alfred
when: name has fred in it.
Moving on to default...
whne: I donnot say a Fred.
二、Perl高階技巧
2.1、切片
對於列表來將,我們往往只需要處理列表中的少量元素,而不必關心其它元素。這時可以通過使用切片來大大降低程式碼量。
下述程式碼是一檔案:bedrock.txt
fred flintstone:2168:301 Cobblestone Way:555-1212:555-2121:3
barney rubble:709918:3128 Granite Blvd:555-3333:555-3438:0
常規的檔案讀寫程式碼如下:
# while(<>){ #讀取檔案
# chomp;
# my @items = split /:/; #根據冒號拆分檔案放於陣列中
# my($card_num, $count) = ($items[1], $items[5]); #賦值
# print "$card_num\t $count\n";
# }
採用切片後簡化程式碼:
while(<>){ #讀取檔案
chomp;
# my (undef, $card_num, undef, undef, undef, $count) = split /:/; #根據冒號拆分檔案
## $card_num = split /:/[1];
## $count = split /:/[5];
my($card_num, $count) = (split /:/)[1,5]; #切片
print "$card_num\t $count\n";
}
#my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat("bedrock.txt");
my($mtime) = (stat("bedrock.txt"))[9]; #切片
print "$mtime\n";
編譯結果:
C:\Users\zhais\Documents\Perl學習>perl slice.pl bedrock.txt
2168 3
709918 0
1612231351
2.1.1、陣列切片
事實上,在從陣列中(而不是列表)切出元素時,圓括號並非必須的。這種寫法稱“陣列切片”。
my @names = qw?zero one two three four five six seven enght nine?;
my($first, $last) = (sort @names)[0, -1]; #先排序再進行切分,-1表示最後一個,也可寫成數字9
print "$first, $last\n";
my @numbers = @names[9,0,2,1,0]; #多切,重複切片(陣列的小括號可以省略)
print "@numbers\n";
print "@names\n";
@names[2,6] = ("2","6"); #通過切片進行修改陣列
print "@names\n";
編譯執行:
enght, zero
nine zero two one zero
zero one two three four five six seven enght nine
zero one 2 three four five 6 seven enght nine
2.1.2、雜湊切片
和陣列切片類似,也可以用雜湊切片來從雜湊裡切出一些元素。
切片一定是列表,所以雜湊切片也可以用@符號來表示。
my %scores = ("barney" => 195, "fred" => 205, "dino" => 30);
my @three_scores = ( $scores{"barney"}, $scores{"fred"}, $scores{"dino"});
print "@three_scores\n";
#雜湊切片
my @three = @scores{ qw/barney fred dino/}; #雜湊切片也可以用@符號,寫上鍵
print "@three\n";
my @players = qw/ barney fred dino/;
my @bowling_scores = (95,105,230);
@scores{ @players} = @bowling_scores;
while(my($k,$v) = each(%scores)){
print "$k => $v\n";
}
編譯執行:
195 205 30
195 205 30
barney => 95
dino => 230
fred => 105
2.2、eval錯誤捕獲
有時程式碼出錯可能會使程式導致嚴重錯誤,致使程式崩潰,停止執行。
Perl提供了簡單的方式來捕獲程式碼執行時可能出現的嚴重錯誤,即把程式碼包裹在eval塊中。如下例,即使 $b是0,這一行程式碼也不至於讓程式崩潰,只要eval發現在它的檢查範圍內出現致命錯誤,就會停止執行這個塊,退出後繼續執行其餘程式碼。
- eval只是一個表示式,大括號末尾必須加上一個分號;
- 當eval塊中出現致命錯誤,停下來的只是這個塊語句,整個程式不會崩潰;
- eval結束時,需要判斷是否正常退出或是否捕捉到錯誤。如果捕獲到錯誤,則會在特殊變數[email protected]中設定錯誤訊息,否則返回undef。
示例1:
$a = 10;
$b = 0;
eval{
$result = $a / $b; #除0錯誤
};
print "Error: [email protected]" if [email protected]有時; #通過eval關鍵字,使得即使出錯,後續程式碼依舊被執行
print "Hello\n";
編譯執行:
Error: Illegal division by zero at eval_1.pl line 5. #捕捉到致命錯誤,依然會繼續執行後續程式碼
Hello
示例2:
sub do_something{
#...
}
foreach my $person(qw/ fred wilam betty dino pebbles/){ #檔案列表
eval{ #捕獲錯誤資訊,即使某一個檔案打開出錯,依然會繼續執行
open FILE, "<$person" or die "Can't open file '$person': $!";
my($total, $count);
while(<FILE>){ #讀取檔案
$total++;
$count++;
}
my $average = $total / $count;
print "Average for file $person was $average\n";
&do_something($person, $average);
};
if([email protected]){
print "An error occured ([email protected]), continuing\n";
}
}
編譯執行:
An error occured (Can't open file 'fred': No such file or directory at eval.pl line 9.
), continuing
An error occured (Can't open file 'wilam': No such file or directory at eval.pl line 9.
), continuing
An error occured (Can't open file 'betty': No such file or directory at eval.pl line 9.
), continuing
An error occured (Can't open file 'dino': No such file or directory at eval.pl line 9.
), continuing
An error occured (Can't open file 'pebbles': No such file or directory at eval.pl line 9.
), continuing
2.3、grep篩選列表元素
grep關鍵字用來篩選列表中的部分成員。比如選出奇數:
my @add_numbers;
foreach(1..100){
push @add_numbers, $_ if $_ % 2; #將基數新增到數組裡
}
print "@add_numbers\n";
print "\n\ngrep...\n";
my @odd = grep $_ % 2, 1..10; #大括號可以省略
print "@odd\n";
- grep第一個引數是程式碼塊,使用$_作為列表的每個元素的佔位符,並返回真或者假值;
- grep第二個引數被篩選的元素列表;
- grep操作符會對列表的每個元素算出程式碼塊的值,程式碼塊計算結果為真的那些元素,將會出現在grep返回的列表中。
2.4、map列表元素變形
map可以將列表元素輸出形式進行改變,類似grep操作符,map操作符也有同樣的兩個引數:
- 第一個引數是使用 $_的程式碼塊,
- 第二個引數是待處理的列表;
my @files = glob "*.*";
print "@files\n";
my @txt_files = map /(.*\.txt)$/, @files; #篩選出.txt檔案
print "@txt_files\n";
my @sizes = map -s, @txt_files; #輸出檔案大小
print "@sizes\n";
編譯執行:
C:\Users\zhais\Documents\Perl學習>perl map.pl
++and--.pl 2_new.png add.pl array.pl array_1.pl array_2.pl bedrock.txt bool.pl caozuofu.pl caozuofu_1.pl chomp.pl circle.pl dir.pl dir_1.pl dir_2.pl dir_3.pl eval.pl eval_1.pl feichuan.pl file.pl file_test.pl given_when.pl grep.pl handle.pl Harry.txt hello.pl if.pl if_1.pl if_control.pl if_file.pl input.pl map.pl module.pl output.pl pattern.pl pattern_1.pl pattern_2.pl pattern_3.pl pattern_4.pl pipei.pl slice.pl sort.pl string.pl string_1.pl string_to_num.pl sub.pl sub_1.pl time.pl
bedrock.txt Harry.txt
120 29399