Лучший способ утверждать, что в Perl определены несколько значений (0 и "" определены) - PullRequest
1 голос
/ 16 июля 2011

Программа, которую я пишу, должна сделать это:

  • прочитать каждую строку файла
  • , если строка содержит упорядоченную пару (x, y), сохранить упорядоченнуюpair
  • перед следующей упорядоченной парой, будет строка файла, которая начинается с «Results»
    • , в которой упорядоченная пара в конце этой строки сохраняется как «значение» и «ошибка "
  • распечатать соответствующее значение x, y, ошибка в формате CSV
  • прочитать следующее (x, y) значение и т. д. (x, y) строки и строки (значение, ошибка) будут чередоваться в файле

Это не домашнее задание.Как видите, у меня уже есть код, который работает до 17 строк.Мне интересно, смогу ли я выполнить эту задачу с меньшим количеством строк или более чистым кодом, сохраняя при этом, по крайней мере, уровень читабельности этой версии и поддерживая стиль Perl (например, разрыв строки между include и первой исполняемой строкой).

Строка, которой я менее всего взволнован:

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

Есть ли лучший способ сделать утверждение, чтобы позаботиться о чередующихся данных в файле?Если я не использую функцию define (), программа не будет работать должным образом, потому что некоторые координаты 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 ]

1 голос
/ 19 июля 2011

Я большой сторонник читабельности, а не краткости.Perl довольно хорошо оптимизирует ваш код, так что вам не нужно об этом беспокоиться.Не беспокойтесь о количестве строк и сохраняйте код читабельным.Все, что вы сохраняете (если вы что-то сохраняете) в ЦП, будет потрачено впустую на время и ошибки, возникающие при попытке сохранить трудно читаемую программу.

В этом отношении:

  • Не используйте пост-суффиксные if операторы, если это не что-то очень простое, например next if (s/^\s*$/);.
  • Используйте имена переменных и не зависят от $_.
  • Используйте пробелы после запятых.

Кроме того, я бы добавил:

  • Не бойтесь добавлять круглые скобки, если они помогают уточнить, что вы делаете.Я склонен использовать круглые скобки, если функции имеют более двух аргументов, просто чтобы помочь удерживать аргументы вместе:

Например:

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

против

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, вы теперьпроверять каждую строку только один раз.Это также позволяет пользователям знать, что каждая строка является либо координатной, либо строкой результата, и что одна строка не является одновременно.В вашей исходной программе вы проверяете каждую строку для обеих, поэтому не было ясно, может ли одна строка быть и тем и другим.

Я также не умираю, если файл не может быть открыт.Вместо этого я печатаю предупреждение и перехожу к следующему.Вы могли бы сделать в любом случае.(Я умер в первом, но продолжаю пахать вперед во втором).

Кстати, ваши предпочтения относительно того, могут ли первые два оператора if комбинироваться вместо вложенных.У меня также есть друг, которому не нравится использовать числовые константы, потому что это легко сказать:

if ($ arguments = SET) {

вместо

if($ координаты == SET) {

Если бы у вас было это:

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

Вы бы привыкли делать это:

if ($coordinates eq SET) {

и нестолкнуться с проблемой = против ==.

1 голос
/ 17 июля 2011

Я бы переключился на чтение двух строк, а не одной:

#!/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";
} 
0 голосов
/ 17 июля 2011

Одно из улучшений, которое вы можете сделать, это просто открыть файлы @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*)\)/;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...