簡體   English   中英

Perl 的地圖有什么意義?

[英]What's the point of Perl's map?

沒有真正理解地圖功能的要點。 任何人都可以用例子解釋它的用途嗎?

使用它代替循環是否有任何性能優勢,或者只是糖?

任何時候你想基於另一個列表生成一個列表:

# Double all elements of a list
my @double = map { $_ * 2 } (1,2,3,4,5);
# @double = (2,4,6,8,10);

由於列表很容易成對轉換為散列,如果您想要基於特定屬性的對象的散列表:

# @user_objects is a list of objects having a unique_id() method
my %users = map { $_->unique_id() => $_ } @user_objects;
# %users = ( $id => $obj, $id => $obj, ...);

這是一個真正通用的工具,您必須開始使用它才能在您的應用程序中找到好的用途。

出於可讀性目的,有些人可能更喜歡冗長的循環代碼,但就個人而言,我發現map更具可讀性。

首先,這是一種轉換數組的簡單方法:而不是說例如

my @raw_values = (...);
my @derived_values;
for my $value (@raw_values) {
    push (@derived_values, _derived_value($value));
}

你可以說

my @raw_values = (...);
my @derived_values = map { _derived_value($_) } @raw_values;

它對於構建快速查找表也很有用:而不是例如

my $sentence = "...";
my @stopwords = (...);
my @foundstopwords;
for my $word (split(/\s+/, $sentence)) {
    for my $stopword (@stopwords) {
       if ($word eq $stopword) {
           push (@foundstopwords, $word);
       }
    }
}

你可以說

my $sentence = "...";
my @stopwords = (...);
my %is_stopword = map { $_ => 1 } @stopwords;
my @foundstopwords = grep { $is_stopword{$_} } split(/\s+/, $sentence);

如果您想從另一個列表中導出一個列表,它也很有用,但不需要特別需要有一個臨時變量來混亂這個地方,例如而不是

my %params = ( username => '...', password => '...', action => $action );
my @parampairs;
for my $param (keys %params) {
    push (@parampairs, $param . '=' . CGI::escape($params{$param}));
}
my $url = $ENV{SCRIPT_NAME} . '?' . join('&', @parampairs);

你說的簡單多了

my %params = ( username => '...', password => '...', action => $action );
my $url = $ENV{SCRIPT_NAME} . '?'
    . join('&', map { $_ . '=' . CGI::escape($params{$_}) } keys %params);

(編輯:修復了最后一行中缺失的“keys %params”)

map函數用於轉換列表。 它基本上是用於替換某些類型的for[each]循環的語法糖。 一旦你把頭環繞在它周圍,你就會看到它無處不在的用途:

my @uppercase = map { uc } @lowercase;
my @hex       = map { sprintf "0x%x", $_ } @decimal;
my %hash      = map { $_ => 1 } @array;
sub join_csv { join ',', map {'"' . $_ . '"' } @_ }

另請參閱Schwartzian 變換以了解地圖的高級用法。

制作查找哈希也很方便:

my %is_boolean = map { $_ => 1 } qw(true false);

相當於

my %is_boolean = ( true => 1, false => 1 );

那里沒有太多節省,但假設您想定義%is_US_state

map用於通過轉換另一個列表的元素來創建列表。

grep用於通過過濾另一個列表的元素來創建列表。

sort用於通過對另一個列表的元素進行排序來創建一個列表。

這些運算符中的每一個都接收一個代碼塊(或表達式),用於轉換、過濾或比較列表的元素。

對於map ,塊的結果成為新列表中的一個(或多個)元素。 當前元素別名為 $_。

對於grep ,塊的布爾結果決定是否將原始列表的元素復制到新列表中。 當前元素別名為 $_。

對於sort ,該塊接收兩個元素(別名為 $a 和 $b)並預期返回 -1、0 或 1 之一,指示 $a 是大於、等於還是小於 $b。

Schwartzian 變換使用這些運算符有效地緩存要用於對列表進行排序的值(屬性),尤其是在計算這些屬性具有非平凡成本時。

它通過創建一個中間數組來工作,該數組具有作為元素數組引用的原始元素和我們想要排序的計算值。 這個數組被傳遞給 sort,它比較已經計算的值,創建另一個中間數組(這個數組已經排序),它又被傳遞給另一個映射,該映射丟棄緩存的值,從而將數組恢復到它的初始列表元素(但是現在按所需的順序)。

示例(在當前目錄中創建按上次修改時間排序的文件列表):

@file_list = glob('*');
@file_modify_times = map { [ $_, (stat($_))[8] ] } @file_list;
@files_sorted_by_mtime = sort { $a->[1] <=> $b->[1] } @file_modify_times;
@sorted_files = map { $_->[0] } @files_sorted_by_mtime;

通過將運算符鏈接在一起,中間數組不需要聲明變量;

@sorted_files = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [ $_, (stat($_))[8] ] } glob('*');

您還可以在排序之前通過插入grep過濾列表(如果您想過濾相同的緩存值):

示例(最近 24 小時內修改的文件列表按最后修改時間排序):

    @sorted_files = map { $_->[0] } sort { $a->[1] <=> $b->[1] } grep { $_->[1] > (time - 24 * 3600 } map { [ $_, (stat($_))[8] ] } glob('*');

map 函數是函數式編程范式中的一個想法。 在函數式編程中,函數是一等對象,這意味着它們可以作為參數傳遞給其他函數。 Map 是一個簡單但非常有用的例子。 它接受一個函數(我們稱之為f )和一個列表l作為它的參數。 f必須是一個帶一個參數的函數,而 map 只是將f應用於列表l每個元素。 f可以對每個元素執行您需要執行的任何操作:向每個元素添加一個,對每個元素進行平方,將每個元素寫入數據庫,或者為每個元素打開一個 Web 瀏覽器窗口,這恰好是一個有效的 URL。

使用map的優點是它很好地封裝了對列表元素的迭代。 你所要做的就是說“對每個元素都做f ,這取決於map決定如何最好地做到這一點。例如, map可能被實現以在多個線程之間拆分它的工作,並且它對呼叫者,召集者。

請注意,該map根本不是特定於 Perl 的。 它是函數式語言使用的標准技術。 它甚至可以使用函數指針在 C 中實現,或者使用“函數對象”在 C++ 中實現。

map 函數對列表的每個元素運行一個表達式,並返回列表結果。 假設我有以下列表

@names = ("andrew", "bob", "carol" );

我想把每個名字的第一個字母大寫。 我可以遍歷它們並調用每個元素的 ucfirst,或者我可以執行以下操作

@names = map (ucfirst, @names);

“只是糖”是苛刻的。 請記住,循環只是糖—— if 和 goto 可以完成循環結構所能做的所有事情,甚至更多。

Map 是一個足夠高級的函數,它可以幫助您在頭腦中處理更復雜的操作,因此您可以編寫和調試更大的問題。

套用 Hall & Schwartz 的“Effective Perl Programming”的話,map 可能會被濫用,但我認為它最好用於從現有列表創建新列表。

創建一個包含 3,2, & 1 的平方的列表:

@numbers = (3,2,1);
@squares = map { $_ ** 2 } @numbers;

生成密碼:

$ perl -E'say map {chr(32 + 95 * rand)} 1..16'
# -> j'k=$^o7\l'yi28G

您使用 map 轉換列表並將結果分配給另一個列表,使用 grep 過濾列表並將結果分配給另一個列表。 “其他”列表可以是與您正在轉換/過濾的列表相同的變量。

my @array = ( 1..5 );
@array = map { $_+5 } @array;
print "@array\n";
@array = grep { $_ < 7 } @array;
print "@array\n";

它允許您將列表轉換為表達式而不是語句 想象一下這樣定義的士兵散列:

{ name          => 'John Smith'
, rank          => 'Lieutenant'
, serial_number => '382-293937-20'
};

那么就可以分別對名字列表進行操作了。

例如,

map { $_->{name} } values %soldiers

是一個表達式 它可以去任何允許表達式的地方——除非你不能給它賦值。

${[ sort map { $_->{name} } values %soldiers ]}[-1]

索引數組,取最大值。

my %soldiers_by_sn = map { $->{serial_number} => $_ } values %soldiers;

我發現操作表達式的優點之一是它減少了來自臨時變量的錯誤。

如果 McCoy 先生想過濾掉所有的 Hatfields 以供考慮,您可以使用最少的編碼添加該檢查。

my %soldiers_by_sn 
    = map  { $->{serial_number}, $_ } 
      grep { $_->{name} !~ m/Hatfield$/ } 
      values %soldiers
      ;

我可以繼續鏈接這些表達式,這樣如果我與這​​些數據的交互必須深入到特定目的,我就不必編寫大量代碼來假裝我要做更多。

任何時候您想從現有列表創建新列表時都可以使用它。

例如,您可以在字符串列表上映射解析函數以將它們轉換為整數。

正如其他人所說, map 從列表中創建列表。 考慮將一個列表的內容“映射”到另一個列表中。 這是來自 CGI 程序的一些代碼,用於獲取專利號列表並打印專利申請的超鏈接:

my @patents = ('7,120,721', '6,809,505', '7,194,673');
print join(", ", map { "<a href=\"http://patft.uspto.gov/netacgi/nph-Parser?Sect1=PTO1&Sect2=HITOFF&d=PALL&p=1&u=/netahtml/srchnum.htm&r=0&f=S&l=50&TERM1=$_\">$_</a>" } @patents);

正如其他人所說, map 對於轉換列表最有用。 沒有提到的是 map 和“等效” for 循環之間的區別。

一個區別是 for 不適用於修改其迭代列表的表達式。 其中一個終止,另一個不終止:

perl -e '@x=("x"); map { push @x, $_ } @x'
perl -e '@x=("x"); push @x, $_ for @x'

另一個小區別是 map 塊內的上下文是一個列表上下文,但 for 循環賦予了一個 void 上下文。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM