Perl的地图有什么意义?
并非真正了解地图功能。谁能举例说明其用法?
使用它代替循环有什么性能上的好处,还是仅仅是糖?
解决方案
任何时候我们要生成基于另一个列表的列表:
# 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
更具可读性。
map函数在列表的每个元素上运行一个表达式,并返回列表结果。可以说我有以下清单
@names = ("andrew", "bob", "carol" );
我想将每个名称的首字母大写。我可以遍历它们并调用每个元素的ucfirst,或者我可以执行以下操作
@names = map (ucfirst, @names);
只要我们想从现有列表中创建新列表,就可以使用它。
例如,我们可以将解析函数映射到字符串列表上,以将其转换为整数。
为了解释Hall&Schwartz的"有效Perl编程",
地图可能会被滥用,但我认为最好将其用于从现有列表中创建新列表。
创建3,2,&1的平方的列表:
@numbers = (3,2,1); @squares = map { $_ ** 2 } @numbers;
有关地图的高级用法,另请参见Schwartzian变换。
进行查找哈希也很方便:
my %is_boolean = map { $_ => 1 } qw(true false);
相当于
my %is_boolean = ( true => 1, false => 1 );
那里没有多少节省,但是假设我们想定义%is_US_state
?
产生密码:
$ perl -E'say map {chr(32 + 95 * rand)} 1..16' # -> j'k=$^o7\l'yi28G
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 {'"' . $_ . '"' } @_ }
首先,这是一种转换数组的简单方法:
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转换列表并将结果分配给另一个列表,使用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先生想过滤掉所有Hatfield供考虑,则可以用最少的编码添加该支票。
my %soldiers_by_sn = map { $->{serial_number}, $_ } grep { $_->{name} !~ m/Hatfield$/ } values %soldiers ;
我可以继续链接这些表达式,这样,如果出于特定目的我必须与这些数据进行深入的交互,则不必编写很多代码来假装自己将做更多的事情。
映射函数是功能编程范式中的一种思想。在函数式编程中,函数是一流的对象,这意味着它们可以作为参数传递给其他函数。地图是一个简单但非常有用的例子。它以一个函数(叫作" f")和一个列表" l"作为参数。 " f"必须是一个带有一个参数的函数,并且map只是将" f"应用于列表" l"的每个元素。 f可以对每个元素做任何我们需要做的事情:向每个元素添加一个,对每个元素进行平方,将每个元素写入数据库,或者为每个元素打开一个网络浏览器窗口,而这恰好是一个有效的URL。
使用map的好处是很好地封装了对列表元素的迭代。我们所要做的就是对每个元素说" f",这取决于map
来决定如何做到最好。例如,可以执行map
将其工作分配到多个线程中,并且这对于呼叫者是完全透明的。
请注意,map
完全不是Perl所特有的。这是功能语言使用的一种标准技术。它甚至可以在C中使用函数指针来实现,或者在C ++中使用"函数对象"来实现。
map用于通过转换另一个列表的元素来创建列表。
grep用于通过过滤另一个列表的元素来创建列表。
sort用于通过对另一个列表的元素进行排序来创建列表。
这些运算符中的每一个都接收一个代码块(或者表达式),该代码块用于转换,过滤或者比较列表的元素。
对于map,该块的结果成为新列表中的一个(或者多个)元素。当前元素的别名为$ _。
对于grep,该块的布尔结果决定是否将原始列表的元素复制到新列表中。当前元素的别名为$ _。
对于排序,该块接收两个元素(别名为$ 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从列表中创建列表。考虑将一个列表的内容"映射"到另一个列表中。这是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);
perl -e '@x=("x"); map { push @x, $_ } @x' perl -e '@x=("x"); push @x, $_ for @x'
"只是糖"是苛刻的。请记住,循环只是糖-如果'和goto可以完成循环构造所做的一切,甚至更多。
Map是一个足够高的级别的函数,它可以进行更复杂的操作,因此我们可以编码和调试更大的问题。