断言在Perl中定义多个值的最佳方法(定义了0和“”)

时间:2011-07-16 19:27:51

标签: regex perl syntax

我写的程序需要这样做:

  • 读取文件的每一行
  • 如果该行包含有序对(x,y),则存储有序对
  • 在下一个有序对之前,将有一行以“结果”开头的文件
    • 将有序对存储在该行的末尾,作为“值”和“错误”
  • 以CSV格式打印相应的x,y,值,错误
  • 读取下一个(x,y)值,依此类推,(x,y)行和(value,error)行将在文件中交替

这不是家庭作业。正如您所看到的,我已经拥有可以运行到17行的代码。我想知道我是否可以使用更少的行或更清晰的代码完成此任务,同时至少保持此版本具有的可读性水平,并保持Perl样式(例如包含和第一个可执行行之间的换行符)。

我最不感兴趣的是

if (defined($x) && defined($y) && defined($val) && defined($err))

有没有更好的方法来执行断言来处理文件中的交替数据?如果我不使用defined()函数,程序将无法正常工作,因为一些x和y坐标是0值。

#!/usr/bin/perl
use strict;

print "X,Y,Val\n";
foreach (@ARGV){
    open log,$_ or die $!;
    my ($x,$y,$val,$err);
    while(<log>){
        chomp;
        ($x,$y) = ($1,$2) if (/\((\d*|-\d*),(\d*|-\d*)\)/);
        ($val,$err) = ($1,$2) if (/^Results.*\((.*),(.*)\)$/);
        if (defined($x) && defined($y) && defined($val) && defined($err)){
            print "$x,$y,$val:$err\n";
            ($x,$y,$val,$err) = undef;
        }
    }
}

谢谢大家的答案,我正在学习很多新的Perl语法。 我已经想出如何将这个脚本减少到10行。我正在挑战自己能够写出这一行的行数。

#!/usr/bin/perl 
use strict;

print "X,Y,Val\n";
open LOG,"<@ARGV[0]" or die $!;
while(<LOG>){
    chomp;
    print "$1,$2," if (/\((\d*|-\d*),(\d*|-\d*)\)/);
    print "$1:$2\n" if (/^Results.*\((.*),(.*)\)$/);
}

另一个更新。使用答案中的信息,我能够将其降低到8行。我还改进了正则表达式,并确保如果提供了多个文件,标题只会被打印一次。

#!/usr/bin/perl
use strict;

while(<>){
    print "X,Y,Val\n" if ($. == 1);
    print "$1,$2," if (/.*\((-?\d+),(-?\d+)\)/);
    print "$1:$2\n" if (/^Results.*\((.*)\).*\((.*)\)$/);
}

3 个答案:

答案 0 :(得分:1)

我会转而阅读两行,而不是一行:

#!/usr/bin/perl

use strict;
use warnings;

use autodie;

print "X,Y,Val\n";
for my $filename (@ARGV) {
    open my $log, "<", $filename;

    while (my $coord_line = <$log>) {
        my ($x, $y) = $coord_line =~ /\((-?[0-9]+),(-?[0-9])\)/
            or die "bad coored line";
        my $results_line = <$log>;
        my ($val,$err) = $results_line =~ /^Results.*\((.*),(.*)\)$/
            or die "bad results line";

        print "$x,$y,$val:$err\n";
    }
}

此方法的一个好处是您的变量现在已适当确定范围。该程序的更简单版本是:

#!/usr/bin/perl

use strict;
use warnings;

use ARGV::readonly; #prevent files like "|ls" from breaking us

print "X,Y,Val\n";
while (<>) {
    my ($x, $y) = /\((-?[0-9]+),(-?[0-9]+)\)/
        or die "bad coored line";
    my ($val,$err) = <> =~ /^Results.*\((.*),(.*)\)$/
        or die "bad results line";

    print "$x,$y,$val:$err\n";
}

考虑到我们关心的两条线之间线条可能性的另一种变体。它假设第一个坐标对是正确的坐标:

#!/usr/bin/perl

use strict;
use warnings;

use ARGV::readonly; #prevent files like "|ls" from breaking us

print "X,Y,Val\n";
while (<>) {
    next unless my ($x, $y) = /\((-?[0-9]+),(-?[0-9]+)\)/;
    my ($val, $err);
    while  (<>) {
        last if ($val, $err) = /^Results.*\((.*),(.*)\)$/;
    }
    die "bad format" unless defined $val;
    print "$x,$y,$val:$err\n";
} 

这个处理你想要最后一个坐标线的情况:

#!/usr/bin/perl

use strict;
use warnings;

use ARGV::readonly; #prevent files like "|ls" from breaking us

print "X,Y,Val\n";
my ($x, $y);
while (<>) {
    ($x, $y) = ($1, $2) if /\((-?[0-9]+),(-?[0-9]+)\)/;
    next unless my ($val, $err) = /^Results.*\((.*),(.*)\)$/;
    print "$x,$y,$val:$err\n";
} 

答案 1 :(得分:1)

我是可读性的主要支持者,而不是简洁。 Perl非常擅长优化代码,因此您不必担心它。不要过分担心行数,并保持代码可读。你节省的(如果你保存任何东西)在CPU时间中会浪费时间和错误,试图维护一个难以阅读的程序。

在这方面:

  • 不要使用后缀if语句,除非它是非常简单的,例如next if (s/^\s*$/);
  • 使用变量名称,不依赖于$_
  • 逗号后使用空格。

最重要的是,我想补充一下:

  • 如果它们有助于澄清您正在做的事情,请不要害怕添加括号。如果函数有两个以上的参数只是为了帮助保持参数,我倾向于使用括号:

例如:

open my $foo, "<", $bar or die qq(This is the end!\n);

VS

open (my $foo, "<", $bar) or die qq(This is the end!\n);

现在更明显的是,open函数中的哪一行是参数。

  

我最不感兴趣的是:

if (defined($x) && defined($y) && defined($val) && defined($err)){

这条线有什么问题?很清楚你想说的是什么。我会使用更现代的语法并添加一些括号来帮助重新组合以使其更清晰:

if ((defined $x) and (defined $y) and (defined $val) and (defined $err)) {

看看你在做什么,我会重新安排一些事情......

#! /usr/bin/env perl

use strict;
use warnings;
use features qw(say);

say "X, Y, Val";

for my $filename (<>) {
    open (my $log, "<", $filename) or die $!;

    my ($x, $y, $value, $err);
    while (chomp (my $coord_line = <$log>)) {
        if ($coord_line =~ /\((-?[0-9]+),(-?[0-9])\)/) {
           ($x, $y) = ($1, $2);
        }
        elsif ($coord_line =~ /^Results.*\((.*),(.*)\)$/) {
           ($val, $err) = ($1, $2);
           say "$x, $y, $val:$err";
        }
    }
}

}

注意我现在只是检查一行。并且,请注意我在获得结果时打印,这样就无需检查是否已设置所有变量。

另请注意,您不需要ARGV::readonly,因为您在open函数中使用了两个以上的参数。在这种情况下,打开文件ls|不会导致任何问题。只有在open语句中只有两个参数时,才会出现此问题。

上述程序假设您只有坐标和结果或垃圾线。但是,如果您有多个坐标, AND ,您只想要第一个坐标,则必须跟踪它们。我建议使用单独的变量来实现此目的,您可以使用常量来帮助澄清您正在做的事情:

#! /usr/bin/env perl

use strict;
use warnings;
use features qw(say);

use autodie;

use constants {
    SET     => 1,
    NOT_SET => 0,
};

say "X, Y, Val";

for my $filename (<>) {
    if (not open my $log, "<", $filename) {
       warn qq(Cannot open file "$filename": $!);
       next;
    }

    my ($x, $y, $value, $err);
    my $coordinates = NOT_SET;
    while (my chomp($coord_line = <$log>)) {
        if ($coord_line =~ /\((-?[0-9]+),(-?[0-9])\)/) {
           if ($coordinates == NOT_SET)) {
               ($x, $y) = ($1, $2);
               $coordinates = SET;
           }
        }
        elsif ($coord_line =~ /^Results.*\((.*),(.*)\)$/) {
           ($val, $err) = ($1, $2);
           say "$x, $y, $val:$err";
           $coordinates = NOT_SET;
        }
    }
}

通过使用if/elsif语句,您现在只检查每一行一次。它还允许用户知道每条线都是坐标线或结果线,并且单条线不是两者。在原始程序中,您要检查两行的每一行,因此不清楚单行是否可以同时存在。

如果无法打开文件,我也不会死。相反,我打印一个警告然后继续下一个。你可以做任何一种方式。 (我在第一次死亡,但在第二次继续前进)。

BTW,您是否可以组合前两个if语句而不是嵌套语句。我也有一个朋友不喜欢使用数字常量,因为这很容易说:

if($ coordinates = SET){

而不是

if($ coordinates == SET){

如果你有这个:

 use constants {
    SET     => "set",
    NOT_SET => "",
 };

你会习惯这样做:

if ($coordinates eq SET) {

并且不要遇到===问题。

答案 2 :(得分:0)

您可以做的一项改进就是直接打开@ARGV文件,如下所示。获取四个目标变量的值时,也可以跳过if语句。您可以使用if-else拆分检查和模式匹配,以保存一些处理,还可以限制$val$err的范围。

此外,您不需要chomp,因为您不使用行或行结尾。

不确定它有多大帮助,但它确实存在。

use warnings;
use strict;

my ($x,$y);
while (<ARGV>) {
    if (defined $x && defined $y) {
        my ($val,$err) = /^Results.*\((.*),(.*)\)$/;
        if (defined $val && defined $err) {
            print "$x,$y,$val:$err\n";
            ($x,$y) = undef;
        }
    } else {
            ($x,$y) = /\((\d*|-\d*),(\d*|-\d*)\)/;
    }
}