学习笔记 Perl grep函数使用揭秘
本文和大家重点讨论一下Perl grep函数的使用,Perl grep函数会根据LIST中的元素对BLOCK或EXPR做出评估,BLOCK块是一个或多个由花括号分隔开的Perl语句。而List则是一串被排序的值。
Perl grep函数的使用
关于Perl grep函数
(如果你是个Perl的新手,你可以先跳过下面的两段,直接到Grepvs.loops样例这一部分,放心,在后面你还会遇到它)
grepBLOCKLIST
grepEXPR,LIST
Perl grep函数会根据LIST中的元素对BLOCK或EXPR做出评估,而且会把局部变量$_设置为当前所用的LIST中的元素。BLOCK块是一个或多个由花括号分隔开的Perl语句。而List则是一串被排序的值。EXPR是一个或多个变量,操作符,字符,函数,子程序调用的综合体。Grep会返回一组经BLOCK或EXPR块的估值后是真的元素。如果BLOCK块由多个语句组成,那么Grep以BLOCK中的最后一条语句的估计值为准。LIST可以是一个列表也可以是一个数组。在标量上下文中,grep返回的是可以被BLOCK或EXPR估为真的元素个数。
请避免在BLOCK或EXPR块中修改$_,因为这会相应的修改LIST中的元素。同时还要避免把grep返回的列表做为左值使用,因为这也会修改LIST中的元素。(所谓左值变量就是一个在赋值表达式左边的变量)。一些Perlhackers可能会利用这个所谓的"特性",但是我建议你不要使用这种混乱的编程风格.
Perl grep函数与循环
这个例子打印出myfile这个文件中含有terriosm和nuclear的行(大小写不敏感).
openFILE"<myfile"ordie"Can'topenmyfile:$!"; printgrep/terrorism|nuclear/i,<FILE>;
对于文件很大的情况,这段代码耗费很多内存。因为grep把它的第二个参数作为一个列表上下文看待,所以<>操作符返回的是整个的文件。更有效的代码应该这样写:
while($line=<FILE>){ if($line=~/terrorism|nuclear/i){print$line} }
通过上面可以看到,使用循环可以完成所有grep可以完成的工作。那为什么我们还要使用grep呢?一个直观的答案是grep的风格更像Perl,而loops(循环)则是C的风格。一个更好的答案是,首先,grep很直观的告诉读者正在进行的操作是从一串值中选出想要的。其次,grep比循环简洁。(用软件工程的说法就是grep比循环更具有内聚力)。基本上,如果你对Perl不是很熟悉,随便你使用循环。否则,你应该多使用像grep这样的强大工具.
计算数组中匹配给定模式的元素个数
在一个标量上下文中,grep返回的是匹配的元素个数.
$num_apple=grep/^apple$/i,@fruits;^和$匹配符的联合使用指定了只匹配那些以apple开头且同时以apple结尾的元素。这里grep匹配apple但是pineapple就不匹配。
输出列表中的不同元素
@unique=grep{++$count{$_}<2} qw(abacddefgfhh); print"@unique\n";
输出结果:abcdefgh$count{$_}是Perl散列中的一个元素,是一个键值对(Perl中的散列和计算机科学中的哈希表有关系,但不完全相同)这里count散列的键就是输入列表中的各个值,而各键对应的值就是该键是否使BLOCK估值为真的次数。当一个值第一次出现的时候BLOCK的值被估为真(因为小于2),当该值再次出现的时候就会被估计为假(因为等于或大于2)。
取出列表中出现两次的值
@crops=qw(wheatcornbarleyricecornsoybeanhay alfalfaricehaybeetscornhay); @duplicates=grep{$count{$_}==2} grep{++$count{$_}>1}@crops; print"@duplicates\n";
在grep的第一个列表元素被传给BLOCK或EXPR块前,第二个参数被当作列表上下文看待。这意味着,第二个grep将在左边的grep开始对BLOCK进行估值之前完全读入count散列。
列出当前目录中的文本文件
@files=grep{-fand-T}glob'*.*';
print"@files\n";
glob函数是独立于操作系统的,它像Unix的shell一样对文件的扩展名进行估计。单个的*表示匹配所以当前目录下不以.开头的文件,.*表示匹配当前目录下以.开头的所有文件.如果一个文件是文本文件-f和-T文件测试符则返回真。使用-fand-T进行测试要比单用-T进行测试有效,因为如果一个文件没有通过-f测试,那么-T测试就不会进行,而-f测试比-T耗时更少.
从数组中选出元素并消除重复
@array=qw(Tobeornottobethatisthequestion); print"@array\n"; @found_words= grep{$_=~/b|o/iand++$counts{$_}<2;}@array; print"@found_words\n";
输出结果:
Tobeornottobethatisthequestion
Tobeornottoquestio
逻辑表达式$_=~/b|o/i匹配包含有b或o的元素(区别大小写)。把匹配操作放在计数工作前要比把计数工作放在前面有效些。比如,如果左边的表达式测试失败,那么右边的表达式就不会被计算.
选出二维坐标数组中横坐标大于纵坐标的元素
#Anarrayofreferencestoanonymousarrays @data_points=([5,12],[20,-3], [2,2],[13,20]); @y_gt_x=grep{$_->[0]<$_->[1]}@data_points; foreach$xy(@y_gt_x){print"$xy->[0],$xy->[1]\n"}
输出结果:
5,12
13,20
在数据库中查找餐馆
这个例子实现数据库的方法不适合在实际中使用的,但是它说明了使用Perl grep函数的时候,只要你的内存够用,BLOCK块的复杂度基本没有限制.
#@databaseisarrayofreferencestoanonymoushashes @database=( {name=>"WildGinger", city=>"Seattle", cuisine=>"AsianThaiChineseKoreanJapanese", expense=>4, music=>"\0", meals=>"lunchdinner", view=>"\0", smoking=>"\0", parking=>"validated", rating=>4, payment=>"MCVISAAMEX", }, #{...},etc. ); subfindRestaurants{ my($database,$query)=@_; returngrep{ $query->{city}? lc($query->{city})eqlc($_->{city}):1 and$query->{cuisine}? $_->{cuisine}=~/$query->{cuisine}/i:1 and$query->{min_expense}? $_->{expense}>=$query->{min_expense}:1 and$query->{max_expense}? $_->{expense}<=$query->{max_expense}:1 and$query->{music}?$_->{music}:1 and$query->{music_type}? $_->{music}=~/$query->{music_type}/i:1 and$query->{meals}? $_->{meals}=~/$query->{meals}/i:1 and$query->{view}?$_->{view}:1 and$query->{smoking}?$_->{smoking}:1 and$query->{parking}?$_->{parking}:1 and$query->{min_rating}? $_->{rating}>=$query->{min_rating}:1 and$query->{max_rating}? $_->{rating}<=$query->{max_rating}:1 and$query->{payment}? $_->{payment}=~/$query->{payment}/i:1 }@$database; } %query=(city=>'Seattle',cuisine=>'Asian|Thai'); @restaurants=findRestaurants(\@database,\%query); print"$restaurants[0]->{name}\n";