简简单单讲map (一)map函数 map BLOCK LIST map EXPR, LIST map函数对LIST里的每个元素按BLOCK或EXPR进行计算,遍历LIST时,临时将LIST里的每个元素赋值给$_变量。map对每次的计算返回一个结果列表,它在列表上下文里计算BLOCK或EXPR。每个LIST元素可能在输出列表里产生0个,1个,或多个元素。 (仙子注:上文是说遍历每个LIST元素时产生一个结果列表,而不是说总的map结果是个列表,不要搞混了哦。) 在标量上下文里,map返回结果列表的元素数量。在HASH上下文里,输出列表(a,b,c,d...)会变成这样的形式: ( a =>; b, c =>; d, ... )。假如输出列表的元素数量非对称,那么最后的hash元素的值就是undef了。 避免在BLOCK或EXPR里修改$_,因为这会修改LIST里的元素。另外,避免使用map返回的的列表作为左值,因为这也会修改LIST里的元素。(所谓左值,就是在某个表达式左边的变量。) (二)Map vs. grep vs. foreach map跟grep一样,从数组里选择元素。下列2句是一样的: @selected = grep EXPR, @input; @selected = map { if (EXPR) { $_ } } @input; 另外,map也是foreach陈述的特殊形式。假如@transformed数组当前未定义或为空,那么下列2句亦相等: foreach (@input) { push @transformed, EXPR; } @transformed = map EXPR, @input; 通常,用grep来从数组里选择元素,用map来从数组里转换元素。当然,数组处理也能使用标准的循环语句来完成(foreach, for, while, until, do while, do until, redo)。 (三)map用法示例 1. 转换文件名为文件大小 @sizes = map { -s $_ } @file_names; -s是个文件测试操作符,它返回某个文件的size。所以上面这句就返回@file_names数组里每个文件的大小,结果也是个数组。 2. 转换数组到hash:找到某个数组值的索引 代替重复的搜索数组,我们可以用map来转换数组到hash,并通过hash关键字来进行直接查找。如下的map用法相对于重复的数组搜索,更简单高效。 @teams = qw(Miami Oregon Florida Tennessee Texas Oklahoma Nebraska LSU Colorado Maryland); %rank = map { $teams[$_], $_ + 1 } 0 .. $#teams; print "Colorado: $rank{Colorado}/n"; print "Texas: $rank{Texas} (hook 'em, Horns!)/n"; 打印结果是: Colorado: 9 Texas: 5 (hook 'em, Horns!) 上述code容易理解哦,0 ..$#teams 是个列表,$#teams代表@teams最后一个元素的下标值(这里是9),所以这个列表就是0-9这几个数了。map遍历上述列表,将每个列表元素临时设置为$_,并对$_在中间的{}里进行计算;{ $teams[$_], $_ + 1 },这里每次计算后返回一个2元素的列表,列表结果是某个数组值和对应的数组下标加1,明白了呀? 由于对每个LIST元素进行计算时,都产生一个2元素的列表,所以总的map结果就可看作一个hash了。hash关键字就是数组元素,hash值是对应的数组下标加1。 3. 转换数组到hash:查找拼错单词 转换数组到hash是map的最普遍用法。在本示例里,hash的值是无关紧要的,我们仅检查hash关键字是否存在。 %dictionary = map { $_, 1 } qw(cat dog man woman hat glove); @words = qw(dog kat wimen hat man gloove); foreach $word (@words) { if (not $dictionary{$word}) { print "Possible misspelled word: $word/n"; } } 打印结果是: Possible misspelled word: kat Possible misspelled word: wimen Possible misspelled word: gloove 看看第1句的map用法,它跟前面示例里的差不多哦。qw()这里是个列表,map对这个列表里的每个元素进行{ $_, 1 }计算,每次计算的结果返回一个2元素的列表,换句话说,就是%dictionary的key和value呀。所以map最终的结果就是一个hash了,关键字是qw()里的元素,值总是1,无关紧要的。 然后下面的foreach语句就容易了哦,如果@words里的元素不构成%dictionary的关键字的话,就打印一条出错消息。如果把%dictionary看成标准字典的话,那么就可用它来检验你自己的@words字库里是否有错字了呀。 4. 转换数组到hash:存储选中的CGI参数 hash通常是存储传递给程序或子函数的参数的最便利的方法,而map通常是创建这个hash的最便利的方法。 use CGI qw(param); %params = map { $_, ( param($_) )[0] } grep { lc($_) ne 'submit' } param(); 这里你可能要了解一下CGI模块的基本知识哦。param()调用返回CGI参数名的列表;param($_)调用返回指定的CGI参数名的值。假如param($_)返回某个CGI参数的多个值,那么( param($_) )[0]只取第一个值,以便hash仍被良好定义。 上述code的意思是,将param()的结果作为输入列表,它的元素是多个CGI参数名,然后从这些参数名里grep出参数名不等于'submit'的,结果是一个临时列表,map的{ $_, ( param($_) )[0] }语句再次遍历这个临时列表,并获取到参数名,和对应的参数值,将结果赋给%params。所以%params里就存储了页面提交过来的,除了submit外的其他CGI参数名和参数值(只取第1个)。 很巧妙的用法,是不是?它结合用了map和grep,使code显得很简洁。 (话外一句:偶在Cornell读书时,偶的师兄们很喜欢这种用法,他们往往在中间多次使用map,grep,sort进行堆叠,结果产生的code也许高效,但不容易看懂。读这样的code时,你要从右往左读,因为右边表达式产生的临时列表,是左边表达式的输入条件。) 5. 产生随机密码 @a = (0 .. 9, 'a' .. 'z'); $password = join '', map { $a[int rand @a] } 0 .. 7; print "$password/n"; 每次运行它会得到不同的结果,但长度总是8位,由0 .. 7这个决定。如下是可能的输出: y2ti3dal 它是个随机值,也许你能用它来做密码。 这里,需要先明白几个函数,rand产生一个随机值,它后面的@a其实是个标量哦,表示@a数组的长度,rand @a的结果可能是个小数,所以再用int函数来取整。int rand @a的结果是个整数,它>;=0但小于@a的长度。所以$a[int rand @a]就表示从@a数组里随机取出一个字符了。0..7表示总共取8次,返回的结果再用join连接起来,就构成一个8位随机密码了呀。 当然,(0 .. 9, 'a' .. 'z')数组元素太少了,你可以修改它,使其包含大小写字符,数字和标点符号,这样密码强度就高些。 6. 从数组元素里剥离数字 已经说了哦,不要在EXPR里修改LIST值。如下做法是不好的: @digitless = map { tr/0-9//d; $_ } @array; 它虽然从数组元素里剥离了数字,但同样破坏了该数组,:( 如下做法是good: @digitless = map { ($x = $_) =~ tr/0-9//d; $x; } @array; 它将tr的结果赋给临时变量$x,并返回$x的值,这样就保护数组了呀。 7. 打印"just another perl hacker",让你晕到家 print map( { chr } ('10611711511603209711011111610410111' . '4032112101114108032104097099107101114') =~ /.../g ), "/n"; 打印的结果是: just another perl hacker chr函数将单个数字转换到相应的ASCII字符。()=~/.../g语法以3个数字长度为单位,分割数字串到新的串列表。 比较无聊的用法,还不如用pack()和unpack(),:P 8. 转置矩阵 @matrix = ( [1, 2, 3], [4, 5, 6], [7, 8, 9] ); foreach $xyz (@matrix) { print "$xyz->;[0] $xyz->;[1] $xyz->;[2]/n"; } @transposed = map { $x = $_; [ map { $matrix[$_][$x] } 0 .. $#matrix ]; } 0 .. $#{$matrix[0]}; print "/n"; foreach $xyz (@transposed) { print "$xyz->;[0] $xyz->;[1] $xyz->;[2]/n"; 打印结果是: 1 2 3 4 5 6 7 8 9 1 4 7 2 5 8 3 6 9 这里稍微有点复杂哦,让我们分2步看看。 @matrix = ( [1, 2, 3], [4, 5, 6], [7, 8, 9] ); foreach $xyz (@matrix) { print "$xyz->;[0] $xyz->;[1] $xyz->;[2]/n"; } 这里不难明白,( [1, 2, 3], [4, 5, 6], [7, 8, 9] ) 是个数组,它的每个元素又是个匿名数组,这样在$xyz遍历数组时,$xyz->;[0],$xyz->;[1],$xyz->;[2]就可以访问到匿名数组里的元素了。所以会打印出: 1 2 3 4 5 6 7 8 9 @transposed = map { $x = $_; [ map { $matrix[$_][$x] } 0 .. $#matrix ]; } 0 .. $#{$matrix[0]}; 这里复杂点,0 .. $#{$matrix[0]}是个列表,$#{$matrix[0]}表示$matrix[0]这个匿名数组的最大下标值,0 .. $#{$matrix[0]}表示矩阵的横向。$x = $_;这里将$_的值赋给$x,为什么呢?因为它后面又有个map嘛,$_的值会改变的,所以要先存储起来。外围的map返回的值是[]里的map计算出来的一个列表,以[]匿名数组形式返回。[]里面的map是这样的,它的输入LIST是0 .. $#matrix, 表示矩阵的纵向了。$matrix[$_][$x]这里先纵再横,就把矩阵值置换了一下。所以返回的结果列表@transposed就包含置换后的矩阵了哦。 是否有点糊涂?那举例看看。这样看可能好点: [1, 2, 3], [4, 5, 6], [7, 8, 9] 外围的map遍历时,先是横向下标遍历,停留在横向0位。然后第二个map,就是纵向下标遍历了,它要遍历所有纵向下标,这样在横向0位,就先返回[1,4,7]的列表了,然后在横向1位,又返回[2,5,8]的列表,最后在横向2位,返回[3,6,9]的列表。 还不明白呀?那偶也讲不清了,自己多想想,:P 9. 查找质数:警示用法 foreach $num (1 .. 1000) { @expr = map { '$_ % ' . $_ . ' &&' } 2 .. int sqrt $num; if (eval "grep { @expr 1 } $num") { print "$num " } } 打印结果是: 1 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 ... 该code能工作,但它如此麻烦,违背了程序最基本的明晰法则。用如下直观的code代替它就可以了呀: CANDIDATE: foreach $num (1 .. 1000) { foreach $factor (2 .. int sqrt $num) { unless ($num % $factor) { next CANDIDATE } } print "$num "; } 记住,让你的Code简洁哦~~ 一) sort函数 sort LIST sort BLOCK LIST sort SUBNAME LIST sort的用法有如上3种形式。它对LIST进行排序,并返回排序后的列表。假如忽略了SUBNAME或BLOCK,sort按标准字串比较顺序来进行(例如ASCII顺序)。如果指定了SUBNAME,它实际上是个子函数的名字,该子函数对比2个列表元素,并返回一个小于,等于,或大于0的整数,这依赖于元素以何种顺序来sort(升序,恒等,或降序)。也可提供一个BLOCK作为匿名子函数来代替SUBNAME,效果是一样的。 被比较的2个元素,会被临时赋值给变量$a和$b。它们以引用传递,所以不要修改$a或$b。假如使用子函数,它不能是递归函数。 (二) 用法示例 1. 以数字顺序sort @array = (8, 2, 32, 1, 4, 16); print join(' ', sort { $a <=>; $b } @array), "/n"; 打印结果是: 1 2 4 8 16 32 与之一样的是: sub numerically { $a <=>; $b }; print join(' ', sort numerically @array), "/n"; 这个很容易理解哦,它只是按自然数的顺序进行sort,偶就不细讲了。 2.1 以ASCII顺序(非字典顺序)进行sort @languages = qw(fortran lisp c c++ Perl python java); print join(' ', sort @languages), "/n"; 打印结果: Perl c c++ fortran java lisp python 这等同于: print join(' ', sort { $a cmp $b } @languages), "/n"; 按ASCII的顺序进行排序,也没什么说的哦。 注意,如果对数字按ASCII顺序进行sort的话,结果可能与你想的不同: print join(' ', sort 1 .. 11), "/n"; 1 10 11 2 3 4 5 6 7 8 9 2.2 以字典顺序sort use locale; @array = qw(ASCII ascap at_large atlarge A ARP arp); @sorted = sort { ($da = lc $a) =~ s/[/W_]+//g; ($db = lc $b) =~ s/[/W_]+//g; $da cmp $db; } @array; print "@sorted/n"; 打印结果是: A ARP arp ascap ASCII atlarge at_large use locale是可选的--它让code兼容性更好,假如原始数据包含国际字符的话。use locale影响了cmp,lt,le,ge,gt和其他一些函数的操作属性--更多细节见perllocale的man page。 注意atlarge和at_large的顺序在输出时颠倒了,尽管它们的sort顺序是一样的(sort中间的子函数删掉了at_large中间的下划线)。这点会发生,是因为该示例运行在perl 5.005_02上。在perl版本5.6前,sort函数不会保护有一样values的keys的先后顺序。perl版本5.6和更高的版本,会保护这个顺序。 注意哦,不管是map,grep还是sort,都要保护这个临时变量$_(sort里是$a和$b)的值,不要去修改它。在该code里,在对$a或$b进行替换操作s/[/W_]+//g前,先将它们重新赋值给$da和$db,这样替换操作就不会修改原始元素哦。 3. 以降序sort 降序sort比较简单,把cmp或<=>;前后的操作数调换下位置就可以了。 sort { $b <=>; $a } @array; 或者改变中间的块或子函数的返回值的标记: sort { -($a <=>; $b) } @array; 或使用reverse函数(这有点低效,但也许易读点): reverse sort { $a <=>; $b } @array; 4. 使用多个keys进行sort 要以多个keys来sort,将所有以or连接起来的比较操作,放在一个子函数里即可。将主要的比较操作放在前面,次要的放在后面。 # An array of references to anonymous hashes @employees = ( { FIRST =>; 'Bill', LAST =>; 'Gates', SALARY =>; 600000, AGE =>; 45 }, { FIRST =>; 'George', LAST =>; 'Tester' SALARY =>; 55000, AGE =>; 29 }, { FIRST =>; 'Steve', LAST =>; 'Ballmer', SALARY =>; 600000, AGE =>; 41 } { FIRST =>; 'Sally', LAST =>; 'Developer', SALARY =>; 55000, AGE =>; 29 }, { FIRST =>; 'Joe', LAST =>; 'Tester', SALARY =>; 55000, AGE =>; 29 }, ); sub seniority { $b->;{SALARY} <=>; $a->;{SALARY} or $b->;{AGE} <=>; $a->;{AGE} or $a->;{LAST} cmp $b->;{LAST} or $a->;{FIRST} cmp $b->;{FIRST} } @ranked = sort seniority @employees; foreach $emp (@ranked) { print "$emp->;{SALARY}/t$emp->;{AGE}/t$emp->;{FIRST} $emp->;{LAST}/n"; } 打印结果是: 600000 45 Bill Gates 600000 41 Steve Ballmer 55000 29 Sally Developer 55000 29 George Tester 55000 29 Joe Tester 上述code看起来很复杂,实际上很容易理解哦。@employees数组的元素是匿名hash。匿名hash实际上是个引用,可使用->;操作符来访问其值,例如$employees[0]->;{SALARY}可访问到第一个匿名hash里SALARY对应的值。所以上述各项比较就很清楚了,先比较SALARY的值,再比较AGE的值,再比较LAST的值,最后比较FIRST的值。注意前2项比较是降序的,后2项是升序的,不要搞混了哦。 5. sort出新数组 @x = qw(matt elroy jane sally); @rank[sort { $x[$a] cmp $x[$b] } 0 .. $#x] = 0 .. $#x; print "@rank/n"; 打印结果是: 2 0 1 3 这里是否有点糊涂呀?仔细看就清楚了。0 .. $#x是个列表,它的值是@x数组的下标,这里就是0 1 2 3。$x[$a] cmp $x[$b] 就是将@x里的各个元素,按ASCII顺序进行比较。所以sort的结果返回对@x的下标进行排序的列表,排序的标准就是该下标对应的@x元素的ASCII顺序。 还不明白sort返回什么?让我们先打印出@x里元素的ASCII顺序: @x = qw(matt elroy jane sally); print join ' ',sort { $a cmp $b } @x; 打印结果是:elroy jane matt sally 它们在@x里对应的下标是1 2 0 3,所以上述sort返回的结果就是1 2 0 3这个列表了。@rank[1 2 0 3] = 0 .. $#x 只是个简单的数组赋值操作,所以@rank的结果就是(2 0 1 3)了。 6. 按keys对hash进行sort %hash = (Donald =>; Knuth, Alan =>; Turing, John =>; Neumann); @sorted = map { { ($_ =>; $hash{$_}) } } sort keys %hash; foreach $hashref (@sorted) { ($key, $value) = each %$hashref; print "$key =>; $value/n"; } 打印结果是: Alan =>; Turing Donald =>; Knuth John =>; Neumann 上述code不难明白哦。sort keys %hash按%hash的keys的ASCII顺序返回一个列表,然后用map进行计算,注意map这里用了双重{{}},里面的{}是个匿名hash哦,也就是说map的结果是个匿名hash列表,明白了呀? 所以@sorted数组里的元素就是各个匿名hash,通过%$hashref进行反引用,就可以访问到它们的key/value值了。 7. 按values对hash进行sort %hash = ( Elliot =>; Babbage, Charles =>; Babbage, Grace =>; Hopper, Herman =>; Hollerith ); @sorted = map { { ($_ =>; $hash{$_}) } } sort { $hash{$a} cmp $hash{$b} or $a cmp $b } keys %hash; foreach $hashref (@sorted) { ($key, $value) = each %$hashref; print "$key =>; $value/n"; } 打印结果是: Charles =>; Babbage Elliot =>; Babbage Herman =>; Hollerith Grace =>; Hopper 本文作者如是说,偶觉得很重要: 与hash keys不同,我们不能保证hash values的唯一性。假如你仅根据values来sort hash,那么当你增或删其他values时,有着相同value的2个元素的sort顺序可能会改变。为了求得稳定的结果,应该对value进行主sort,对key进行从sort。 这里{ $hash{$a} cmp $hash{$b} or $a cmp $b } 就先按value再按key进行了2次sort哦,sort返回的结果是排序后的keys列表,然后这个列表再交给map进行计算,返回一个匿名hash列表。访问方法与前面的相同,偶就不详叙了。 8. 对文件里的单词进行sort,并去除重复的 perl -0777ane '$, = "/n"; / @uniq{@F} = (); print sort keys %uniq' file 大家试试这种用法,偶也不是很明白的说,:( @uniq{@F} = ()使用了hash slice来创建一个hash,它的keys是文件里的唯一单词;该用法在语意上等同于$uniq{ $F[0], $F[1], ... $F[$#F] } = ()。 各选项说明如下: -0777 - 读入整个文件,而不是单行 -a - 自动分割模式,将行分割到@F数组 -e - 从命令行读取和运行脚本 -n - 逐行遍历文件:while (<>;) { ... } $, - print函数的输出域分割符 file - 文件名 9. 高效sorting: Orcish算法和Schwartzian转换 对每个key,sort的子函数通常被调用多次。假如非常在意sort的运行时间,可使用Orcish算法或Schwartzian转换,以便每个key仅被计算1次。 考虑如下示例,它根据文件修改日期来sort文件列表。 # 强迫算法--对每个文件要多次访问磁盘 @sorted = sort { -M $a <=>; -M $b } @filenames; # Orcish算法--在hash里创建keys @sorted = sort { ($modtimes{$a} ||= -M $a) <=>; ($modtimes{$b} ||= -M $b) } @filenames; 很巧妙的算法,是不是?因为文件的修改日期在脚本运行期间是基本不变的,所以-M运算一次后,把它存起来就可以了呀。偶就经常这么用的,:p 如下是Schwartzian转换的用法: @sorted = map( { $_->;[0] } sort( { $a->;[1] <=>; $b->;[1] } map({ [$_, -M] } @filenames) ) ); 这个code结合用了map,sort分了好几层,记住偶以前提过的方法,从后往前看。map({ [$_, -M] } @filenames)返回一个列表,列表元素是匿名数组,匿名数组的第一个值是文件名,第二个值是文件的修改日期。 sort( { $a->;[1] <=>; $b->;[1] }...再对上述产生的匿名数组列表进行sort,它根据文件的修改日期进行sort。sort返回的结果是经过排序后的匿名数组。 最外围的map( { $_->;[0] }...就简单了,它从上述sort产生的匿名数组里提取出文件名。这个文件名就是根据修改日期进行sort过的呀,并且每个文件只运行了一次-M。 这就是著名的Schwartzian转换,这种用法在国外perl用户里很流行。记住仙子告诉你的Schwartzian概念哦,下次就不会被老外laugh at了,:p 本文作者说: Orcish算法通常更难于编码,并且不如Schwartzian转换文雅。我推荐你使用Schwartzian转换作为可选择的方法。 也请记住基本的优化code的规则:(1)不写code;(2)在使code快速之前,先保证其正确;(3)在使code快速之前,先让它清楚。 10. 根据最后一列来对行进行sort(Schwartzian转换) 假如$str的值如下(每行以/n终结): eir 11 9 2 6 3 1 1 81% 63% 13 oos 10 6 4 3 3 0 4 60% 70% 25 hrh 10 6 4 5 1 2 2 60% 70% 15 spp 10 6 4 3 3 1 3 60% 60% 14 按最后1个域的大小进行sort: $str = join "/n", map { $_->;[0] } sort { $a->;[1] <=>; $b->;[1] } map { [ $_, (split)[-1] ] } split //n/, $str; 打印结果是: eir 11 9 2 6 3 1 1 81% 63% 13 spp 10 6 4 3 3 1 3 60% 60% 14 hrh 10 6 4 5 1 2 2 60% 70% 15 oos 10 6 4 3 3 0 4 60% 70% 25 让我们从后往前,一步一步看上述code: split //n/, $str; 这里返回一个列表,列表元素就是各个行了。 map { [ $_, (split)[-1] ] } 这里的map求得一个匿名数组列表,匿名数组的值分别是整行,和该行的最后一列。使用Schwartzian转换时,这步是关键哦,记着用map来构造你自己的匿名数组列表,匿名数组的第1个元素是最终需要的值,第2个元素是用于比较的值。 sort { $a->;[1] <=>; $b->;[1] } 对上1步中产生的匿名数组,按第2个元素进行sort,它返回sort后的匿名数组列表。 map { $_->;[0] } 对上1步中sort后的匿名数组,提取出第1个元素,也就是整行哦。 $str = join "/n", 把上步中的各行用"/n"连接起来,并赋值给$str。 也许你会说:“怎么这么麻烦呀?偶不想用这种方式。”那么,可用CPAN上的现成模块来代替: use Sort::Fields; @sorted = fieldsort [ 6, '2n', '-3n' ] @lines; CPAN的模块文档很详细的,自己看看呀。 11. 重访高效sorting: Guttman-Rosler转换 考虑如下示例: @dates = qw(2001/1/1 2001/07/04 1999/12/25); 你想按日期升序对它们进行排序,哪种方法最有效呢? 最直观的Schwartzian转换可以这样写: @sorted = map { $_->;[0] } sort { $a->;[1] <=>; $b->;[1] or $a->;[2] <=>; $b->;[2] or $a->;[3] <=>; $b->;[3] } map { [ $_, split m</>; $_, 3 ] } @dates; 然而,更高效的Guttman-Rosler转换(GRT)这样写: @sorted = map { substr $_, 10 } sort map { m|(/d/d/d/d)/(/d+)/(/d+)|; sprintf "%d-%02d-%02d%s", $1, $2, $3, $_ } @dates; 本文作者说: GRT方法难于编码,并且比Schwartzian转换更难阅读,所以我推荐仅在极端环境下使用GRT。使用大的数据源,perl 5.005_03和linux 2.2.14进行测试,GRT比Schwartzian转换快1.7倍。用perl 5.005_02和windows NT 4.0 SP6进行测试,GRT比Schwartzian快2.5倍。 另外,perl 5.6及更高版本的sort使用Mergesort算法,而5.6之前的sort使用Quicksort算法,前者显然快于后者,所以,要想求速度,也要升级你的perl版本哦。 (三)CPAN上关于sort的一些模块 File::Sort - Sort one or more text files by lines Sort::Fields - Sort lines using one or more columns as the sort key(s) Sort::ArbBiLex - Construct sort functions for arbitrary sort orders Text::BibTeX::BibSort - Generate sort keys for bibliographic entries. (一) Grep函数 grep有2种表达方式: grep BLOCK LIST grep EXPR, LIST BLOCK表示一个code块,通常用{}表示;EXPR表示一个表达式,通常是正则表达式。原文说EXPR可是任何东西,包括一个或多个变量,操作符,文字,函数,或子函数调用。 LIST是要匹配的列表。 grep对列表里的每个元素进行BLOCK或EXPR匹配,它遍历列表,并临时设置元素为$_。在列表上下文里,grep返回匹配命中的所有元素,结果也是个列表。在标量上下文里,grep返回匹配命中的元素个数。 (二) Grep vs. loops open FILE "<myfile" or die "Can't open myfile: $!"; print grep /terrorism|nuclear/i, <FILE>;; 这里打开一个文件myfile,然后查找包含terrorism或nuclear的行。<FILE>;返回一个列表,它包含了文件的完整内容。 可能你已发现,如果文件很大的话,这种方式很耗费内存,因为文件的所有内容都拷贝到内存里了。 代替的方式是使用loop(循环)来完成: while ($line = <FILE>;) { if ($line =~ /terrorism|nuclear/i) { print $line } } 上述code显示,loop可以完成grep能做的任何事情。那为什么还要用grep呢?答案是grep更具perl风格,而loop是C风格的。 更好的解释是:(1)grep让读者更显然的知道,你在从列表里选择某元素;(2)grep比loop简洁。 一点建议:如果你是perl新手,那就规矩的使用loop比较好;等你熟悉perl了,就可使用grep这个有力的工具。 (三) 几个grep的示例 1. 统计匹配表达式的列表元素个数 $num_apple = grep /^apple$/i, @fruits; 在标量上下文里,grep返回匹配中的元素个数;在列表上下文里,grep返回匹配中的元素的一个列表。 所以,上述code返回apple单词在@fruits数组中存在的个数。因为$num_apple是个标量,它强迫grep结果位于标量上下文里。 2. 从列表里抽取唯一元素 @unique = grep { ++$count{$_} < 2 } qw(a b a c d d e f g f h h); print "@unique/n"; 上述code运行后会返回:a b c d e f g h 即qw(a b a c d d e f g f h h)这个列表里的唯一元素被返回了。为什么会这样呀?让我们看看: %count是个hash结构,它的key是遍历qw()列表时,逐个抽取的列表元素。++$count{$_}表示$_对应的hash值自增。在这个比较上下文里,++$count{$_}与$count{$_}++的意义是不一样的哦,前者表示在比较之前,就将自身值自增1;后者表示在比较之后,才将自身值自增1。所以,++$count{$_} < 2 表示将$count{$_}加1,然后与2进行比较。$count{$_}值默认是undef或0。所以当某个元素a第一次被当作hash的关键字时,它自增后对应的hash值就是1,当它第二次当作hash关键字时,对应的hash值就变成2了。变成2后,就不满足比较条件了,所以a不会第2次出现。 所以上述code就能从列表里唯一1次的抽取元素了。 2. 抽取列表里精确出现2次的元素 @crops = qw(wheat corn barley rice corn soybean hay alfalfa rice hay beets corn hay); @duplicates = grep { $count{$_} == 2 } grep { ++$count{$_} >; 1 } @crops; print "@duplicates/n"; 运行结果是:rice 这里grep了2次哦,顺序是从右至左。首先grep { ++$count{$_} >; 1 } @crops;返回一个列表,列表的结果是@crops里出现次数大于1的元素。 然后再对产生的临时列表进行grep { $count{$_} == 2 }计算,这里的意思你也该明白了,就是临时列表里,元素出现次数等于2的被返回。 所以上述code就返回rice了,rice出现次数大于1,并且精确等于2,明白了吧? :-) 3. 在当前目录里列出文本文件 @files = grep { -f and -T } glob '* .*'; print "@files/n"; 这个就很容易理解哦。glob返回一个列表,它的内容是当前目录里的任何文件,除了以'.'开头的。{}是个code块,它包含了匹配它后面的列表的条件。这只是grep的另一种用法,其实与 grep EXPR,LIST 这种用法差不多了。-f and -T 匹配列表里的元素,首先它必须是个普通文件,接着它必须是个文本文件。据说这样写效率高点哦,因为-T开销更大,所以在判断-T前,先判断-f了。 4. 选择数组元素并消除重复 @array = qw(To be or not to be that is the question); @found_words = grep { $_ =~ /b|o/i and ++$counts{$_} < 2; } @array; print "@found_words/n"; 运行结果是:To be or not to question {}里的意思就是,对@array里的每个元素,先匹配它是否包含b或o字符(不分大小写),然后每个元素出现的次数,必须小于2(也就是1次啦)。 grep返回一个列表,包含了@array里满足上述2个条件的元素。 5. 从二维数组里选择元素,并且x<y # An array of references to anonymous arrays @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 这里,你应该理解匿名数组哦,[]是个匿名数组,它实际上是个数组的引用(类似于C里面的指针)。 @data_points的元素就是匿名数组。例如: foreach (@data_points){ print $_->;[0];} 这样访问到匿名数组里的第1个元素,把0替换成1就是第2个元素了。 所以{ $_->;[0] < $_->;[1] }就很明白了哦,它表示每个匿名数组的第一个元素的值,小于第二个元素的值。 而grep { $_->;[0] < $_->;[1] } @data_points; 就会返回满足上述条件的匿名数组列表。 所以,就得到你要的结果啦! 6. 简单数据库查询 grep的{}复杂程度如何,取决于program可用虚拟内存的数量。如下是个复杂的{}示例,它模拟了一个数据库查询: # @database is array of references to anonymous hashes @database = ( { name =>; "Wild Ginger", city =>; "Seattle", cuisine =>; "Asian Thai Chinese Korean Japanese", expense =>; 4, music =>; "/0", meals =>; "lunch dinner", view =>; "/0", smoking =>; "/0", parking =>; "validated", rating =>; 4, payment =>; "MC VISA AMEX", }, # { ... }, etc. ); sub findRestaurants { my ($database, $query) = @_; return grep { $query->;{city} ? lc($query->;{city}) eq lc($_->;{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"; 运行结果是:Wild Ginger 上述code不难看懂,但仙子不推荐使用这样的code,一是消耗内存,二是难于维护了。 (alexru注,以上内容均源自bbs.chinaunix.net) |