1. 程式人生 > 其它 >(十三)智慧匹配與given-when結構、高階Perl技巧(~~、陣列/雜湊切片、eval/grep/map運用等)

(十三)智慧匹配與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實現(continuebreak使用)################
given(@ARGV[0]){      #從命令列輸入
  when( /fred/i){   say "when: name has fred in it." ; continue}       #分支中可以新增breakcontinue關鍵字
  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}       #分支中可以新增breakcontinue關鍵字
    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