【问题标题】:Perl: Can someone explain this code? It involves map, sort, tr and references. (Modified Schwartzian Transform)Perl:有人能解释一下这段代码吗?它涉及map、sort、tr和references。 (修正的 Schwartzian 变换)
【发布时间】:2013-08-04 01:29:26
【问题描述】:

我已经阅读了关于 map、tr 和 references 的教程和 perldoc,但是对于像我这样的 Perl 初学者来说,这段代码有点太高级了。

print map $_->[1], 
sort {
$a->[0] cmp $b->[0] ##first element of the array
or $a->[1] cmp $b->[1] } 
map [ tr/"MATCH"/"MATCH"/, $_ ], @allmatches; 

所以我特别需要的是:$_ 指的是什么(未定义?)

最后一行包括地图是做什么的?

我还不太了解 $a 和 $b 的概念。他们指的是什么? @allmatches 的第一个和下一个元素?

另外,所有逗号(在地图之后)有什么作用?如果这就像一个 Schwartzian 变换,那很好,因为我还不明白,尽管阅读过。

这是我的想法:
将未定义的标量映射为对数组(哪个?)的引用,同时调用第二个元素:[1]。它首先按“匹配”的出现次数然后按字母对我的@allmatches 数组进行排序。通过参考制作的第二张地图对我来说很粗糙(地图一步完成了很多工作); tr 返回次数。第二个“MATCH”没用,为什么呢?

奖励:我可以用什么替换 tr/// 以按更多排序,例如:tr/MATCH #\d+// ??

【问题讨论】:

    标签: perl


    【解决方案1】:

    从右到左读取(即按照执行顺序)...

    map [ tr/"MATCH"/"MATCH"/, $_ ], @allmatches;
    

    对于@allmatches 的每个元素e,这会创建一个对双元素数组的引用,该数组的第一个元素是数字,第二个元素是e。映射的结果是这些引用的数组。

    tr/"MATCH"/"MATCH"/ 正在计算字母 M、A、T、C 或 H 在 e 中出现的次数。 (从技术上讲,它是用 M 替换 M,用 A 替换 A,用 T 替换 T,等等,并计算它做了多少次这样的替换。)

    实际上,它也在计算引号字符,因为 tr/// 将像处理其他任何内容一样处理这些字符。这似乎是一个错误。

    无论如何,假设每个引用都指向一个数组 [n,e],其中 n 是奇怪的计数,e 是 @allmatches 的原始元素。

    “排序”然后对引用数组进行排序,主要按 n(解释为字符串,而不是数字;这似乎是另一个错误),其次按字符串 e。

    最后,最外面的“map”在排序完成后,从每个双元素数组中提取第二个元素(e)。所以最终的结果就是对@allmatches 的元素做一个奇怪的(我相信,有问题的)排序。

    [编辑:正如 cjm 在评论中指出的那样,这个 map sort map 成语被称为 Schwartzian transform。]

    【讨论】:

    • 很好...我认为方括号产生数组引用,而不是数组,但tr/// 的分析比我的要好。而且你在最后一次地图操作上也做得更好。干得好。
    • @Jonathan:你是对的。我已经编辑了我的答案以更准确。谢谢。
    • +1 进行解释。我觉得你是对的;这是一个错误。 cmp 会将整数值转换为字符串,因此 10 <=> 2 返回 110 cmp 2 返回 -1(因为 '10' lt '2')。
    • 非常感谢您的清理,我不知道它是按 MATCH 的每个字母搜索的...现在我需要找到一种方法来按整个单词的频率和一些分隔符进行排序... tr 似乎不会这样做。
    • 解释是正确的,但你还应该提到这个成语(map sort map)被称为Schwartzian transform,用于通过一个可能很昂贵的键对数组进行有效排序.
    【解决方案2】:

    不要从右到左阅读;更好地格式化它(原件很糟糕),然后从下往上阅读:

    print map  { $_->[1] }
          sort {
                  $b->[0] <=> $a->[0]
                          ||
                  $a->[1] cmp $b->[1]
               }
          map  { [ tr/MATCH// => $_ ] }
          @allmatches;
    

    或者使用更灵活的哈希代替:

    print map  { $_->{DATA} }
          sort {
                  $b->{COUNT} <=> $a->{COUNT}
                              ||
                  $a->{DATA}  cmp $b->{DATA}
               }
          map  {
                 +{
                    COUNT  => tr/MATCH//,
                    DATA   => $_,
                  }
          } @allmatches;
    

    当然和这个是一样的:

    print map  {         $$_{DATA}      }
          sort {
                  $$b{COUNT} <=> $$a{COUNT}
                              ||
                  $$a{DATA}  cmp $$b{DATA}
               }
          map  {
                 +{
                      COUNT  => tr/MATCH//,
                      DATA   => $_,
                  }
          } @allmatches;
    

    看看这有多好?另外,当您从下到上阅读时,它对应于一个非常简单的 shell 样式数据流:

      map @allmatches | sort | map | print
    

    这比这更容易理解

      print(map(sort(map @allmatches)))
    

    这也是为什么大家更喜欢 shell 的数据流模型的原因。

    【讨论】:

    • 感谢我可以实施的一些具体更改,并且使用这些快捷方式可以清理干净。现在我想我可以用循环和正则表达式替换 tr/// 以获得更好的频率排序。谢谢!
    【解决方案3】:

    哎呀,同样糟糕......

    print map $_->[1], 
                sort {
              $a->[0] cmp $b->[0] ##first element of the array
              or $a->[1] cmp $b->[1] } 
          map [ tr/"MATCH"/"MATCH"/, $_ ], @allmatches;
    

    sort 部分相对简单。

    sort { $a->[0] cmp $b->[0] or $a->[1] cmp $b->[1] } ...an array...
    

    数组的每个元素本身就是一个数组 ref,比较对数组 refs 的第一个元素进行字符串比较(cmp),如果它们相等(cmp 返回 0),则第二个元素。

    因此,输出是一个排序数组。这留下了两块代码需要剖析。第一行和最后一行。最后一行运行map

    map [ tr/"MATCH"/"MATCH"/, $_ ], @allmatches
    

    这显然是在进行无操作转换,因为tr/// 运算符中的左右字符串是相同的;这有点令人费解。 [更新tr/// 计算每个字母 MATCH 在字符串中出现的次数;在 map 的 'block' 或 'expr' 中,$_ 是一个特殊变量 - 被映射的值。] 但它获取 @allmatches 的每个元素并对其进行映射,并将其输出传递给种类。方括号形成一个数组 ref,所以输出是一个数组 refs 的数组;每个数组引用都包含单词中 MATCH 的字母数的计数,后跟单词。

    那么第一行是:

    print map $_->[1], ...output from sort...;
    

    这会从排序后的输出中提取名称 $_-&gt;[1]

    • 总体而言,效果是按顺序列出 @allmatches 中的单词,使得 MATCH 中字母最少(可能为零)的单词首先出现,按字母顺序排列,其次是 MATCH 中字母最少的单词MATCH(再次按字母顺序),依此类推。

    这是一个压缩的巡回演出如果有人将它提供给我进行审查,他们会走回绘图板。更新:因为这是一个众所周知的习语(Schwartzian Transform),发回它的唯一原因是“布局不够仔细”和“没有注释为 Schwartzian 变换”。)

    # Schwartzian Transform: sort by number of letters from MATCH and alphabetically
    print map  { $_->[1] } 
          sort { $a->[0] <=> $b->[0] or $a->[1] cmp $b->[1] } 
          map  { [ tr/"MATCH"/"MATCH"/, $_ ] }
          @allmatches;
    

    (这正确地使用了第一项的数字比较。)

    您提到对$a$b 感到困惑。它们基本上是魔术变量——排序中比较函数的参数。如果$a 比较小于$b,则比较必须返回负值,如果$a 比较大于$b,则返回正值,如果它们比较相等,则返回零。它们($a$b)是需要两个名称时使用的名称; $_map(以及 grep 和其他列表转换函数)一起使用,只需要一个名称。

    【讨论】:

    • +1。您的答案与我试图放在一起的答案基本相同,但我也对 tr 的使用感到困惑:)。我还要提到 $_ 也是一个特殊变量,并且不是未定义的
    • 非常感谢您提供的深入解决方案。清理了很多,我认为数组引用的数组把我搞砸了。
    • tr 返回一个整数。打电话给cmp而不是&lt;=&gt;是很危险的。
    • @tchrist,我不认为“危险”是正确的词;它只是没有做你想做的事(除非你的要求真的很奇怪)。
    • @cjm:嗯,"2" 将排在"11" 之后,这不太可能产生令人愉快的结果。看我的回答。
    猜你喜欢
    • 2013-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-17
    • 2017-05-12
    • 2023-03-26
    相关资源
    最近更新 更多