學習筆記 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";
輸出結果:WildGinger
【編輯推薦】