3

データファイルを解析し、1100 x 1300 グリッドで満たされた 5 つの出力ファイルを書き込む Perl スクリプトがあります。スクリプトは機能しますが、私の意見では、ぎこちなく、おそらく非効率的です。スクリプトも継承されたコードであり、読みやすくするために少し変更しています。それでも、それは混乱です。

現時点では、スクリプトはデータファイル (~4Mb) を読み取り、それを配列に配置します。次に、配列をループして内容を解析し、値を別の配列にプッシュし、最後にそれらを別の for ループでファイルに出力します。特定のポイントの値が見つからない場合は、9999 が出力されます。ゼロは許容値です。

データファイルには 5 つの異なるパラメーターがあり、それぞれが独自のファイルに書き込まれます。

データの例:

data for the param: 2
5559
// (x,y) count values
280 40 3  0 0 0 
280 41 4  0 0 0 0 
280 42 5  0 0 0 0 0 
281 43 4  0 0 10 10 
281 44 4  0 0 10 10 
281 45 4  0 0 0 10 
281 46 4  0 0 10 0 
281 47 4  0 0 10 0 
281 48 3  10 10 0 
281 49 2  0 0 
41 50 3  0 0 0 
45 50 3  0 0 0 
280 50 2  0 0 
40 51 8  0 0 0 0 0 0 0 0
...

data for the param: 3
3356
// (x,y) count values

5559は、現在のパラメーターへのデータ行の数です。データ行は次のようになります: xy、その特定のポイントの連続する x 値の数、そして最後にvalues。パラメータ間に空行があります。

先ほど言ったように、スクリプトは機能しますが、これははるかに簡単かつ効率的に実行できると思います。方法がわかりません。ですから、ここが自己改善のチャンスです。

配列と for ループの複雑な組み合わせよりも、この問題へのより良いアプローチは何でしょうか?

編集

これについてもっと明確にするべきでした、ごめんなさい。

出力は、データ ファイルから読み取った値で満たされた 1100 x 1300 グリッドです。各パラメータは異なるファイルに書き込まれます。データ行の複数の値は、その行に x(+n), y ポイントのデータがあることを意味します。

更新

ソリューションをテストしたところ、驚いたことに、元のスクリプトよりも遅くなりました (~3 秒)。ただし、スクリプトは最大 50% 小さいため、スクリプトの機能を実際に理解するのがはるかに簡単になります。この場合、それは 3 秒の速度向上よりも重要です。

古いスクリプトのコードの一部を次に示します。そこから基本的な考え方を理解していただければ幸いです。なぜ速いのですか?

 for my $i (0..$#indata) { # Data file is read to @indata
 ...
   if($indata[$i] =~ /^data for the param:/) { 
     push @block, $i;  #  data borders aka. lines, where block starts and ends
   }
 ...
 }
  # Then handle the data blocks
 for my $k (0..4) {  # 5 parameters
 ...
   if( $k eq '4') {  # Last parameter
     $enddata = $#indata;
   }
   else {
     $enddata = $block[$k+1];
   }
    ...
   for my $p ($block[$k]..$enddata) { # from current block to next block 
    ...
   # Fill data array
    for(my $m=0 ; $m<$n ; $m++){
    $data[$x][$y] = $values[$m];
     }

   }
   print2file();

 }
4

3 に答える 3

1

以下は、ハッシュのスパース配列を埋めます。印刷時、9999値が定義されていないセルを印刷します。メモリ フットプリントを削減するために、各行を文字列として構築するようにコードを変更しました。

#!/usr/bin/perl

use strict; use warnings;
use YAML;

use constant GRID_X => 1100 - 1;
use constant GRID_Y => 1300 - 1;

while (my $data = <DATA> ) {
    if ( $data =~ /^data for the param: (\d)/ ) {
        process_param($1, \*DATA);
    }
}

sub process_param {
    my ($param, $fh) = @_;
    my $lines_to_read = <$fh>;
    my $lines_read = 0;

    $lines_to_read += 0;

    my %data;

    while ( my $data = <$fh> ) {
        next if $data =~ m{^//};
        last unless $data =~ /\S/;
        $lines_read += 1;

        my ($x, $y, $n, @vals) = split ' ', $data;

        for my $i ( 0 .. ($n - 1) ) {
            $data{$x + $i}{$y} = 0 + $vals[$i];
        }
    }
    if ( $lines_read != $lines_to_read ) {
        warn "read $lines_read lines, expected $lines_to_read\n";
    }

    # this is where you would open a $param specific output file
    # and write out the full matrix, instead of printing to STDOUT
    # as I have done. As an improvement, you should probably factor
    # this out to another sub.

    for my $x (0 .. GRID_X) {
        my $row;
        for my $y (0 .. GRID_Y) {
            my $v = 9999;
            if ( exists($data{$x})
                    and exists($data{$x}{$y})
                    and defined($data{$x}{$y}) ) {
                $v = $data{$x}{$y};
            }
            $row .= "$v\t";
        }
        $row =~ s/\t\z/\n/;
        print $row;
    }

    return;
}


__DATA__
data for the param: 2
5559
// (x,y) count values
280 40 3  0 0 0 
280 41 4  0 0 0 0 
280 42 5  0 0 0 0 0 
281 43 4  0 0 10 10 
281 44 4  0 0 10 10 
281 45 4  0 0 0 10 
281 46 4  0 0 10 0 
281 47 4  0 0 10 0 
281 48 3  10 10 0 
281 49 2  0 0 
41 50 3  0 0 0 
45 50 3  0 0 0 
280 50 2  0 0 
40 51 8  0 0 0 0 0 0 0 0
于 2010-10-22T14:14:14.530 に答える
0

参照を使用する場合、Perlは多次元配列をサポートします。

my $matrix = [];
$matrix->[0]->[0] = $valueAt0x0;

だからあなたは一度に全部を読むことができました

$matrix = [];
while($ln = <INPUT>) {
  @row = split(/ /, @ln); #assuming input is separated by spaces
  push(@$matrix, \@row);
}
# here you read matrix.  Let's print it
foreach my $row (@$matrix) {
  print join(",", @{$row}) . "\n";
}
# now you pruinted your matrix with "," as a separator

お役に立てれば。

于 2010-10-22T13:44:56.877 に答える
0

目的の出力を記述していないため、ファイルに何を書き込むべきかを知ることは不可能です。しかし、これは非常に柔軟な方法で読み取り部分を行います。おそらく、正規表現の数をマイクロ最適化するか、$_読みやすさを向上させるために暗黙的なトピック変数の使用を失う可能性があります。flush_output を呼び出す前に、行列の各セルに対して特定の出力形式 (「すべての値をカンマで結合」など) にコミットする場合は、配列の最内層を廃止して、$matrix[$x][$y] .= ($matrix[$x][$y] ? ',' : '') . join(',', @data);または同様のことを行うことができます。あいまいではありません。

use strict;
use warnings;

my $cur_param;
my @matrix;
while (<DATA>) {
  chomp;
  s/\/\/.*$//;
  next if /^\s*$/;

  if (/^data for the param: (\d+)/) {
    flush_output($cur_param, \@matrix) if defined $cur_param;
    $cur_param = $1;
    @matrix = (); # reset
    # skip the line with number of rows, we're smarter than that
    my $tmp = <DATA>;
    next;
  }

  (my $x, my $y, undef, my @data) = split /\s+/, $_;
  $matrix[$x][$y] ||= [];
  push @{$matrix[$x][$y]}, @data;
}

sub flush_output {
  my $cur_param = shift;
  my $matrix = shift;
  # in reality: open file and dump
  # ... while dumping, do an ||= [9999] for the default...

  # here: simple debug output:
  use Data::Dumper;
  print "\nPARAM $cur_param\n";
  print Dumper $matrix;
}

__DATA__
data for the param: 2
5559
// (x,y) count values
280 40 3  0 0 0 
280 41 4  0 0 0 0 
280 42 5  0 0 0 0 0 
281 43 4  0 0 10 10 
281 44 4  0 0 10 10 
281 45 4  0 0 0 10 
281 46 4  0 0 10 0 
281 47 4  0 0 10 0 
281 48 3  10 10 0 
281 49 2  0 0 
41 50 3  0 0 0 
45 50 3  0 0 0 
280 50 2  0 0 
40 51 8  0 0 0 0 0 0 0 0

data for the param: 3
3356
// (x,y) count values
于 2010-10-22T14:07:47.477 に答える