perl中grep,sort,map用法总结



简简单单讲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)
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值