我不是很理解map函数的作用。谁能举例说明一下使用它的场景呢?
相较于使用循环,使用map函数是否有性能上的优势,还是只是为了让代码更简洁易读?
我不是很理解map函数的作用。谁能举例说明一下使用它的场景呢?
相较于使用循环,使用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 {'"' . $_ . '"' } @_ }
这对创建查找哈希表也很方便:
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 Transform使用这些运算符有效地缓存要在排序列表时使用的值(属性),特别是计算这些属性具有非平凡成本时。
它的工作原理是创建一个中间数组,其中的元素为带有原始元素和计算出的要排序的值的数组引用。该数组被传递给sort,它比较已经计算的值,创建另一个中间数组(这个已排序),然后传递给另一个map,将缓存的值丢弃,从而将数组恢复为其初始列表元素(但现在是按所需顺序排列的)。
例子(创建当前目录中按最后修改时间排序的文件列表):
@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('*');
f
)和一个列表l
作为参数。f
必须是一个接受一个参数的函数,而map仅将f
应用于列表l
的每个元素。f
可以对每个元素执行任何您需要执行的操作:将每个元素加一、将每个元素平方、将每个元素写入数据库或者为每个元素打开一个Web浏览器窗口,这恰好是一个有效的URL。map
的优点在于它很好地封装了迭代列表元素的过程。你只需要说“对每个元素做f
”,而由map
决定如何最好地做到这一点。例如,map
可以实现将其工作分配给多个线程,并且对调用者完全透明。map
并不特定于Perl。它是函数式语言使用的标准技术。甚至可以使用函数指针在C中实现它,在C++中使用“函数对象”实现它。“只是糖”很严厉。请记住,循环结构只不过是语法糖—使用if和goto语句可以完成循环结构的所有功能,甚至更多。
映射是足够高级的函数,它可以帮助您在头脑中处理更复杂的操作,这样您就可以编写和调试更大的问题了。
map函数在列表的每个元素上运行一个表达式,并返回列表结果。假设我有以下列表:
@names = ("andrew", "bob", "carol" );
我想将这些名称的第一个字母大写。我可以循环遍历它们并调用每个元素的ucfirst,或者我可以只做以下操作:
@names = map (ucfirst, @names);
@numbers = (3,2,1);
@squares = map { $_ ** 2 } @numbers;