perl中grep,sort,map用法總結
阿新 • • 發佈:2019-02-16
簡簡單單講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)