(十三)智能匹配与given-when结构、高级Perl技巧(~~、数组/哈希切片、eval/grep/map运用等)

一、智能匹配与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结束时,需要判断是否正常退出或是否捕捉到错误。如果捕获到错误,则会在特殊变量$@中设置错误消息,否则返回undef。

示例1:

$a = 10;
$b = 0;

eval{
  $result = $a / $b;                        #除0错误
};
print "Error: $@" if $@有时;                #通过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($@){
    print "An error occured ($@), 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;                         #**大括号**可以省略
# 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SD.ZHAI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值