【问题标题】:What's the point of Perl's map?Perl 的地图有什么意义?
【发布时间】:2008-09-25 21:17:31
【问题描述】:

没有真正理解地图功能的意义。谁能用例子解释一下它的用法?

使用它而不是循环是否有任何性能优势,或者它只是糖?

【问题讨论】:

    标签: list perl dictionary higher-order-functions


    【解决方案1】:

    任何时候你想生成一个基于另一个列表的列表:

    # 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 也是一个纯粹的解决方案。 for 是一个语句,而 map 是一个表达式。将操作定义为表达式而不是语句可以减少副作用,并允许代码更容易线程化或分叉,并且问题更少。
    【解决方案2】:

    首先,这是一种转换数组的简单方法:而不是说例如

    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 可能会更好地完成您的停用词示例。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      有关地图的高级用法,另请参阅Schwartzian transform

      【讨论】:

        【解决方案5】:

        制作查找哈希也很方便:

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

        等价于

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

        那里没有多少节省,但假设您想定义%is_US_state

        【讨论】:

        • Set::Object 更快,但 API 更好。
        【解决方案6】:

        ma​​p 用于通过转换另一个列表的元素来创建一个列表。

        grep 用于通过过滤另一个列表的元素来创建一个列表。

        sort 用于通过对另一个列表的元素进行排序来创建一个列表。

        这些运算符中的每一个都接收一个代码块(或一个表达式),用于转换、过滤或比较列表中的元素。

        对于ma​​p,块的结果成为新列表中的一个(或多个)元素。当前元素的别名为 $_。

        对于grep,块的布尔结果决定是否将原始列表的元素复制到新列表中。当前元素的别名为 $_。

        对于sort,该块接收两个元素(别名为$a 和$b)并预期返回-1、0 或1 之一,指示$a 是否更大、等于或小于 $b。

        Schwartzian Transform 使用这些运算符来有效地缓存用于对列表进行排序的值(属性),尤其是在计算这些属性的成本很高时。

        它的工作原理是创建一个中间数组,该数组具有作为元素的数组引用,原始元素和我们想要排序的计算值。这个数组被传递给排序,它比较已经计算的值,创建另一个中间数组(这个是排序的),然后传递给另一个映射,该映射丢弃缓存的值,从而将数组恢复到它的初始列表元素(但是现在按所需的顺序)。

        示例(在当前目录中创建一个按上次修改时间排序的文件列表):

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

        【讨论】:

          【解决方案7】:

          map 函数是函数式编程范式的一个想法。在函数式编程中,函数是一等对象,这意味着它们可以作为参数传递给其他函数。地图是一个简单但非常有用的例子。它的参数是一个函数(我们称之为f)和一个列表lf 必须是一个接受一个参数的函数,并且 map 只是将 f 应用于列表 l 的每个元素。 f 可以对每个元素做任何你需要做的事情:对每个元素添加一个,对每个元素求平方,将每个元素写入数据库,或者为每个元素打开一个 Web 浏览器窗口,这恰好是一个有效的 URL。

          使用map 的优点是它很好地封装了对列表元素的迭代。您所要做的就是对每个元素说“f”,由map 来决定如何最好地做到这一点。例如,map 可以实现为在多个线程之间拆分其工作,并且这对调用者来说是完全透明的。

          注意,map 并不是 Perl 特有的。它是函数式语言使用的标准技术。它甚至可以在 C 中使用函数指针来实现,或者在 C++ 中使用“函数对象”来实现。

          【讨论】:

            【解决方案8】:

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

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

            我想把每个名字的第一个字母大写。我可以遍历它们并调用每个元素的 ucfirst,或者我可以执行以下操作

            @names = map (ucfirst, @names);
            

            【讨论】:

              【解决方案9】:

              “只是糖”是苛刻的。请记住,循环只是糖——if's 和 goto 可以完成循环构造所做的所有事情,甚至更多。

              Map 是一个足够高级别的函数,它可以帮助您在头脑中掌握更复杂的操作,因此您可以编写和调试更大的问题。

              【讨论】:

                【解决方案10】:

                套用 Hall & Schwartz 的“Effective Perl Programming”, map 可能会被滥用,但我认为它最好用于从现有列表创建新列表。

                创建一个包含 3,2 和 1 的平方的列表:

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

                【讨论】:

                  【解决方案11】:

                  生成密码:

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

                  【讨论】:

                    【解决方案12】:

                    您使用 map 转换列表并将结果分配给另一个列表,使用 grep 过滤列表并将结果分配给另一个列表。 “其他”列表可以是与您正在转换/过滤的列表相同的变量。

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

                    【讨论】:

                      【解决方案13】:

                      它允许您将列表转换为表达式,而不是语句。想象一下这样定义的士兵散列:

                      { 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
                            ;
                      

                      我可以继续链接这些表达式,这样如果我与这​​些数据的交互必须深入到特定目的,我就不必编写很多代码来假装我会做更多的事情。

                      【讨论】:

                        【解决方案14】:

                        在您想从现有列表创建新列表的任何时候都可以使用它。

                        例如,您可以在字符串列表上映射解析函数以将它们转换为整数。

                        【讨论】:

                          【解决方案15】:

                          正如其他人所说,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);
                          

                          【讨论】:

                            【解决方案16】:

                            正如其他人所说,map 对于转换列表最有用。没有提到的是 map 和“等效” for 循环之间的区别。

                            一个区别是 for 不适用于修改其迭代的列表的表达式。其中一个终止,另一个没有:

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

                            另一个小的区别是map块内的context是一个列表上下文,但是for循环赋予了一个void上下文。

                            【讨论】:

                              猜你喜欢
                              • 2011-10-29
                              • 1970-01-01
                              • 2013-07-21
                              • 2011-01-26
                              • 1970-01-01
                              • 2011-09-20
                              • 1970-01-01
                              • 2020-05-21
                              • 1970-01-01
                              相关资源
                              最近更新 更多