如何使用map清理此Perl代码?

时间:2016-12-06 14:36:34

标签: perl

以下代码可以满足我的需求。它打印列表并在不连续的行的末尾添加星号,例如如果你从1到3或3到5跳过。

use strict;
use warnings;
#note: thanks to all who helped with formatting issues.

#note: I recognize a hash would be a much better option for what I want to do.
my @printy = ("1 -> this",
  "5 -> that",
  "3 -> the other",
  "6 -> thus and such");
@printy = sort {num($a) <=> num($b)} @printy;

my $thisID = 0;
my $lastID = 0; 

#print out (line)* if initial number is >1 more than previous, or just (line) otherwise
for (@printy)
{ 
$thisID = $_; $thisID =~s/ .*//g;
if ($thisID - $lastID != 1) { $_ =~ s/$/ \*/; }
$lastID = $thisID;
}
print join("\n", @printy) . "\n";

sub num
{
  my $x = $_[0];
  $x =~ s/ .*//;
  return $x;
}

但我认为我可以做得更好。感觉很纠结,我的直觉告诉我,我错过了一些能够更容易完成工作的强大功能,可能需要两行。

现在我以前使用过map()命令,但只是查看/修改元素,而不是它与前一个元素的比较。任何人都可以推荐一种方法来使这更简洁吗?谢谢!

6 个答案:

答案 0 :(得分:7)

由于Perl推广TIMTOWTDI,map起初可能看起来很有吸引力。让我们看看它如何为这项任务付出代价:

Schwartzian思想过程

  1. 由于需要访问相邻元素,因此使用索引很方便。由于对于n个元素,有n-1个邻居对,因此您不必循环n次。在这种情况下,让我们从1开始,而不是通常的0

    1 .. $#printy
    
  2. 可以通过调用map内的相关索引来访问相邻元素。

    map { my $prev = $printy[$_-1]; my $curr = $printy[$_] } 1 .. $#printy;
    

    数组切片更简洁地表达了这一点:

    map { my ( $prev, $curr ) = @printy[$_-1,$_]; } 1 .. $#printy;
    
  3. 让我们介绍使用num子程序比较数字的真实逻辑:

    map {
           my ( $prev, $curr ) = @printy[$_-1,$_];
           if ( num($curr) - num($prev) > 1 ) {
              "$curr *";
           }
           else {
              $curr;
           }
        } 1 .. $#printy;
    

    相当于:

    map {
           my ( $prev, $curr ) = @printy[$_-1,$_];
           $curr .= " *" if num($curr) - num($prev) > 1;
           $curr
        } 1 .. $#printy;
    
  4. 记住不要忘记第一个元素:

    @printy = ( $printy[0],
                map {
                       my ( $prev, $curr ) = @printy[$_-1,$_];
                       $curr .= " *" if num($curr) - num($prev) > 1;
                       $curr
                    } 1 .. $#printy
              );
    
  5. 鉴于最终结果,我不太确定我会使用map

    • 很难阅读
    • 有很多事情发生在
    • 下一个处理代码的人会爱你

答案 1 :(得分:4)

不需要地图,只需在此处添加一些空格,并删除不需要的内容($ _,加入等)。此外,在循环内重用num(),无需重复正则表达式。

#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

my @printy = sort { num($a) <=> num($b) }
                  '1 -> this', '5 -> that', '3 -> the other', '6 -> thus and such';
my $thisID = my $lastID = 0;

for (@printy) {
    $thisID = num($_);
    $_ .= ' *' if $thisID - $lastID != 1;
    $lastID = $thisID;
}
say for @printy;

sub num {
    my ($x) = @_;
    $x =~ s/ .*//;
    return $x;
}

此外,使用num而不是替换重新实现/(\d+)/可能会更清楚地说明其目的。

答案 2 :(得分:3)

我同意choroba认为这里不需要map。但无论如何我还要重构一下。

use strict;
use warnings;
use feature 'say';

my @printy = ( "1 -> this", "5 -> that", "3 -> the other", "6 -> thus and such" );

my $last_id = 0;
foreach my $line ( sort { num($a) <=> num($b) } @printy ) {
    my $current_id = num($line);
    $line .= ' *' unless $current_id - $last_id == 1;
    $last_id = $current_id;
}
say for @printy;

# returns the number at the start of a string
sub num {
    $_[0] =~ m/^(\d+)/;
    return $1;
}
  • 我将sort向下移动到foreach,因为您不应该依赖于输入已排序的事实。

  • 我更改了变量名称以符合变量名称中不应该包含大写字母的约定,我使用了say,就像print一样,系统特定的换行符最后。

  • 我还将$current_id移到了循环中。这并不需要在外面可见,因为它是该循环的词汇。始终在尽可能小的范围内声明变量。

  • 您已经拥有了很好的num功能,但是您并未在循环内使用它来获取$current_id。使用它。

我认为如果输入变得很长,那么使用map构造可能是有意义的,因为在某些时候排序会非常昂贵。在排序之前,请查看Schwartzian transform以缓存计算。然后你可以一次完成所有事情。但它不再适合初学者。

答案 3 :(得分:3)

您的数据大喊“#34;使用哈希!&#34;对我来说。

如果我们有哈希,

my %printy =
   map { split / -> / }
      "1 -> this", "5 -> that", "3 -> the other", "6 -> thus and such";

解决方案就是:

my @order = sort { $a <=> $b } keys(%printy); 
for my $i (@order[1..$#order]) {
   $printy{$i} .= ' *'
      if !exists($printy{$i-1});
}

print "$_ -> $printy{$_}\n"
   for @order;

虽然我不确定它是否物有所值,但可以打高尔夫球。

my $count;
print "$_ -> $printy{$_}".( !$count++ || exists($printy{$_-1}) ? "" : " *" )."\n"
   for
      sort { $a <=> $b }
         keys(%printy);

for可以转换为map,但效率会降低。

my $count;
print
   map { "$_ -> $printy{$_}".( !$count++ || exists($printy{$_-1}) ? "" : " *" )."\n" }
      sort { $a <=> $b }
         keys(%printy);

答案 4 :(得分:2)

我还建议清理代码并保持循环。但是,这是基于map的方式。

代码使用已排序的@printynum子。

my @nums = map { num($_) } @printy;

my @res = map { 
    $nums[$_] == $nums[$_-1] + 1              # invariably false for $_ == 0
        ? $printy[$_] : $printy[$_] .= ' *'; 
}
(0..$#printy);

say for @res;

这适用于第一个元素,因为它不是在最后一个元素之后,因为我们已经排序了。虽然这可能有点恶魔,但它需要在代码中进行评论。所以也许更好地拼出来

my @res = map { 
    ($nums[$_] == $nums[$_-1] + 1) ? $printy[$_] : $printy[$_] .= ' *'; 
}
(1..$#printy);
unshift @res, $printy[0];

不是很干净但很清楚。

当然,与直线循环相比,所有这些都有额外的功效。

答案 5 :(得分:0)

对不起,但您的代码很乱,而且您需要做的远不止使用map来清理此代码

你在一行上有 no indentation 和多个语句,而你没有考虑过你的逻辑。您的代码无法维护

以下是我写这个的方法。它构建一个并行的ID数组,然后对索引列表进行排序,以便ID和原始数据都按顺序排列

如果它让你更开心,它确实包括map

use strict;
use warnings 'all';

my @data = ( '1 -> this', '5 -> that', '3 -> the other', '6 -> thus and such' );

my @ids = map { /(\d+)/ } @data;

my @indexes = sort { $ids[$a] <=> $ids[$b] } 0 .. $#ids;

my $last_id;

for my $i ( @indexes ) {

    print $data[$i];
    print ' *' if defined $last_id and $ids[$i] > $last_id + 1;
    print "\n";

    $last_id = $ids[$i];
}

输出

1 -> this
3 -> the other *
5 -> that *
6 -> thus and such