Perl的map有什么作用?

34

我不是很理解map函数的作用。谁能举例说明一下使用它的场景呢?

相较于使用循环,使用map函数是否有性能上的优势,还是只是为了让代码更简洁易读?

16个回答

46

每当您想根据另一个列表生成列表时:

# 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更易读。


1
我发现地图比循环更易读。啰嗦并不一定会转化为更好的可读性。 - Svante
1
我同意你的看法,我的意图是将那个决定留给读者。 - Adam Bellaire
在测试中,脚本内的 map 函数似乎比循环执行速度快得多。 - DevilWAH
1
Map也是解决问题的纯粹方案。For是一个语句,而map是一个表达式。将操作定义为表达式而不是语句可以减少副作用,并允许编写更易于线程化或分叉的代码,且出现问题的可能性更小。 - Horus

29

首先,这是一种简单的数组转换方式:不需要像以下示例那样:

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”缺失的问题)


我认为你在最后一个示例中漏掉了"keys %params"。另外,你的停用词示例可能最好使用正则表达式来完成,而不是split/grep。 - cjm

22

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 {'"' . $_ . '"' } @_ }

18

12

这对创建查找哈希表也很方便:

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

等同于

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

那里并没有很多节省的空间,但假设你想要定义%is_US_state


Set::Object更快且具有更好的API。 - jrockway

12

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('*');

6
地图函数是来自函数式编程范例的一个概念。在函数式编程中,函数是一等公民,这意味着它们可以作为参数传递给其他函数。Map是其中一个简单但非常有用的例子。它以一个函数(我们称之为f)和一个列表l作为参数。f必须是一个接受一个参数的函数,而map仅将f应用于列表l的每个元素。f可以对每个元素执行任何您需要执行的操作:将每个元素加一、将每个元素平方、将每个元素写入数据库或者为每个元素打开一个Web浏览器窗口,这恰好是一个有效的URL。
使用map的优点在于它很好地封装了迭代列表元素的过程。你只需要说“对每个元素做f”,而由map决定如何最好地做到这一点。例如,map可以实现将其工作分配给多个线程,并且对调用者完全透明。
请注意,map并不特定于Perl。它是函数式语言使用的标准技术。甚至可以使用函数指针在C中实现它,在C++中使用“函数对象”实现它。

5

“只是糖”很严厉。请记住,循环结构只不过是语法糖—使用if和goto语句可以完成循环结构的所有功能,甚至更多。

映射是足够高级的函数,它可以帮助您在头脑中处理更复杂的操作,这样您就可以编写和调试更大的问题了。


5

map函数在列表的每个元素上运行一个表达式,并返回列表结果。假设我有以下列表:

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

我想将这些名称的第一个字母大写。我可以循环遍历它们并调用每个元素的ucfirst,或者我可以只做以下操作:

@names = map (ucfirst, @names);

4
为了改写 Hall & Schwartz 的 "Effective Perl Programming",map 可以被滥用,但我认为它最好的用途是从一个现有的列表中创建一个新的列表。
创建一个由 3、2 和 1 的平方组成的列表:
@numbers = (3,2,1);
@squares = map { $_ ** 2 } @numbers;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接