繁体   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