這裡講述的是perl的map函式。
mapBLOCK,LIST
mapexpr,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里的元素。
Mapvs.grepvs.foreach
map跟grep一樣,從數組裡選擇元素。下列2句是一樣的:
@selected=grepEXPR,@input;
@selected=map{if(EXPR){$_}}@input;
另外,map也是foreach陳述的特殊形式。假如@transformed數組當前未定義或為空,那么下列2句亦相等:
foreach(@input){push@transformed,EXPR;}
@transformed=mapEXPR,@input;
通常,用grep來從數組裡選擇元素,用map來從數組裡轉換元素。當然,數組處理也能使用標準的循環語句來完成(foreach,for,while,until,dowhile,dountil,redo)。
map用法示例
1.轉換檔案名稱為檔案大小
@sizes=map{-s$_}@file_names;
-s是個檔案測試操作符,它返回某個檔案的size。所以上面這句就返回@file_names數組裡每個檔案的大小,結果也是個數組。
2.轉換數組到hash:找到某個數組值的索引
代替重複的搜尋數組,我們可以用map來轉換數組到hash,並通過hash關鍵字來進行直接查找。如下的map用法相對於重複的數組搜尋,更簡單高效。
@teams=qw(MiamiOregonFloridaTennesseeTexas
OklahomaNebraskaLSUColoradoMaryland);
%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(catdogmanwomanhatglove);
@words=qw(dogkatwimenhatmangloove);
foreach$word(@words){
if(not$dictionary{$word}){
print"Possiblemisspelledword:$word\n";
}
}
列印結果是:
Possiblemisspelledword:kat
Possiblemisspelledword:wimen
Possiblemisspelledword: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的最便利的方法。
useCGIqw(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顯得很簡潔。
5.產生隨機密碼
@a=(0..9,'a'..'z');
$password=join'',map{$a[intrand@a]}0..7;
print"$password\n";
每次運行它會得到不同的結果,但長度總是8位,由0..7這個決定。如下是可能的輸出:
y2ti3dal
它是個隨機值,也許你能用它來做密碼。
這裡,需要先明白幾個函式,rand產生一個隨機值,它後面的@a其實是個標量喔,表示@a數組的長度,rand@a的結果可能是個小數,所以再用int函式來取整。intrand@a的結果是個整數,它>;=0但小於@a的長度。所以$a[intrand@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.列印"justanotherperlhacker"
printmap({chr}
('10611711511603209711011111610410111'.
'4032112101114108032104097099107101114')
=~/.../g
),"\n";
列印的結果是:
justanotherperlhacker
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";
列印結果是:
123
456
789
147
258
369
這裡稍微有點複雜喔,讓我們分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]就可以訪問到匿名數組裡的元素了。所以會列印出:
123
456
789
@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]的列表。
9.查找質數:警示用法
foreach$num(1..1000){
@expr=map{'$_%'.$_.'&&'}2..intsqrt$num;
if(eval"grep{@expr1}$num"){print"$num"}
}
列印結果是:
12357111317192329313741434753596167...
該code能工作,但它如此麻煩,違背了程式最基本的明晰法則。用如下直觀的code代替它就可以了呀:
CANDIDATE:foreach$num(1..1000){
foreach$factor(2..intsqrt$num){
unless($num%$factor){nextCANDIDATE}
}
print"$num";
}