このスクリプトは、テスト ケースを実行します。期待される出力は客観的に間違っていることに注意してください。ファイル 2、1 行目、20 列目に値0.2
が存在します。
#!perl
use 5.010; # just for `say`
use strict; use warnings;
use Test::More;
# define input files + expected outcome
my $file_1_contents = <<'_FILE1_';
0.1
0.11
0.12
0.13
0.14
0.15
0.16
0.17
0.18
0.19
0.2
_FILE1_
my $file_2_contents = <<'_FILE2_';
0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 0.1 0.11 0.12 0.13 0.14 0.15 0.16 0.17 0.18 0.19 0.2 0.21 0.22 0.23 0.24 0.25 0.26 0.27 0.28
1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.18 1.19 1.2 1.21 1.22 1.23 1.24 1.25 1.26 1.27 1.28
_FILE2_
my $expected_output = <<'_OUTPUT_';
1 2 0 0 0 0 0 0 0 10 11 12 13 14 15 16 17 18 19 20 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
_OUTPUT_
# open the filehandles
open my $file1, "<", \$file_1_contents or die "$!";
open my $file2, "<", \$file_2_contents or die "$!";
open my $expected, "<", \$expected_output or die "$!";
my %file1 = map { chomp; 0+$_ => undef } <$file1>;
while (<$file2>) {
chomp;
my @vals = split;
# If value exists in file1, print the col number.
my $line = join " " => map { exists $file1{0+$vals[$_]} ? $_+1 : 0 } 0 .. $#vals;
chomp(my $expected_line = <$expected>);
is $line, $expected_line;
}
done_testing;
まったく同じ出力を 28 個のファイルに出力するには、テスト コードを削除します。
say {$_} $line for @filehandles;
代わりは。
古い答え
あなたの既存のコードは非常に非効率的で単調です。問題を解決するお手伝いをさせてください。
まず、すべての Perl スクリプトを で開始します。use strict; use warnings;
最新の perl (v10 以降) を使用している場合は、use 5.010;
(またはバージョンが何であれ) 追加機能を有効にすることができます。
chomp
呼び出しは変数を取り、現在の値 (通常は改行) を文字列の末尾から削除します$/
。readline オペレーターはそれを行わないため、これは重要です。定数変数の宣言には適していません。むしろ、する
my $file = "File1.txt";
my $hspfle = "File2.txt";
では、use strict
変数を適切に宣言する必要があります。これは、 で行うことができますmy
。
ファイルを開くには、次のイディオムを使用する必要があります。
open my $fh, "<", $filename or die "Can't open $filename: $!";
代わりに、スクリプトの先頭にor die ...
できます。use autodie
これにより、ファイルを開けない場合はスクリプトが中止され、理由が表示され ( $!
)、明示的なオープン モードが指定されます (ここでは<
= 読み取り)。これにより、ファイル名に特殊文字が含まれるバグが回避されます。
レキシカル ファイルハンドル (my
ベアワード ファイルハンドルとは対照的に、変数内) は適切なスコープを持ち、それ自体を閉じます。それらを使用する理由は他にもさまざまです。
このsplit
関数は、最初の引数として文字列ではなく、正規表現を取ります。プログラムを詳しく調べると、split
各要素が@hsporg
28 回、各要素が@k_org
28 × @hsporg 回であることがわかります。これは非常に遅く、事前に行うことができるため不要です。
条件が false の場合、再度明示的に false をテストする必要はありません。
if ($h_org[0] eq $orginfo[0]) {
...;
} elsif ($h_org[0] ne $orginfo[0]) {
...;
}
と$a ne $b
まったく同じnot $a eq $b
です。
Perl で a を使用するのはかなり一義的でありgoto
、どこかのラベルへのジャンプも特に高速ではありません。ラベルは主にループ制御に使用されます。
# random example
LOOP: for my $i (1 .. 10) {
for my $j (1 .. 5) {
next if $i == $j; # start next iteration of current loop
next LOOP if 2 * $i == $j; # start next iteration of labeled loop
last LOOP if $i + $j == 13;# like `break` in C
}
redo
ループ制御動詞は に似ていnext
ますが、ループ条件がある場合でも再チェックしません。
これらのループ制御機能と、囲んでいるループから抜け出す機能があるため、フラグを維持したり複雑な goto を実行したりする必要はほとんどありません。
実際のアルゴリズムをあまり修正せずに、スクリプトをクリーンアップしたバージョンを次に示します。
#!/usr/bin/perl
use strict; use warnings;
use autodie; # automatic error messages
my ($file, $hspfile) = ("File1.txt", "file2.txt");
open my $fh1, "<", $file;
open my $fh2, "<", $hspfile;
my @k_org = <$fh1>;
my @hsporg = <$fh2>;
# Presplit the contents of the arrays:
for my $arr (\@k_org, \@hsporg) {
for (@$arr) {
chomp;
$_ = [ split /\t/ ]; # put an *anonymous arrayref* into each slot
}
}
my $output_files = 28;
for my $z (1 .. $output_files) {
open my $out, ">", "$z.txt";
H_ORG:
for my $h_org (@hsporg) {
my $i = 0;
ORGINFO:
for my $orginfo (@k_org) {
# elements in array references are accessed like $arrayref->[$i]
if($h_org->[0] eq $orginfo->[0]) {
print $out "$z\n";
$i = 1;
last ORGINFO; # break out of this loop
} elsif($h_org->[0] =~ /(\w+\s\w+)\s/ and $orginfo->[0] eq $1) {
print $out "0";
$i = 1;
last ORGINFO;
}
}
print $out "0" if not $i;
}
}
# filehandles are closed automatically.
ここで、さらに最適化できます。各行では、最初の要素のみを使用します。これは、残りを保存する必要がないことを意味します。
...;
for (@$arr) {
chomp;
$_ = (split /\t/, $_, 2)[0]; # save just the first element
}
...;
ORGINFO:
for my $orginfo (@k_org) {
# elements in array references are accessed like $arrayref->[$i]
if($h_org eq $orginfo) {
...;
} elsif($h_org =~ /(\w+\s\w+)\s/ and $orginfo eq $1) {
...;
}
}
また、スカラーへのアクセスは、配列要素へのアクセスよりも少し高速です。
split
結果のフラグメントの数を制限する3 番目の引数。最初のフィールドのみに関心があるため、残りも分割する必要はありません。
次にlast
、ループから抜け出しORGINFO
、フラグをチェックします。H_ORG
これは不要です。フラグを設定する代わりに、ループの次の繰り返しに直接ジャンプできます。ループから自然に脱落した場合ORGINFO
、フラグが設定されていないことが保証されるため、print
とにかく実行できます。
H_ORG:
for my $h_org (@hsporg) {
for my $orginfo (@k_org) {
if($h_org eq $orginfo) {
print $out "$z\n";
next H_ORG;
} elsif($h_org =~ /(\w+\s\w+)\s/ and $orginfo eq $1) {
print $out "0";
next H_ORG;
}
}
print $out "0";
}
次に、同じデータを 28 回比較して、異なるファイルに出力します。ベター: 2 つのサブルーチンprint_index
とを定義しprint_zero
ます。これらの内部で、出力ファイルハンドルをループします。
# make this initialization *before* you use the subs!
my @filehandles = map {open my $fh, ">", "$_.txt"; $fh} 1 .. $output_files;
...; # the H_ORG loop
sub print_index {
for my $i (0 .. $#filehandles) {
print {$filehandles[$i]} $i+1, "\n";
}
}
sub print_zero {
print {$_} 0 for @filehandles;
}
それで:
# no enclosing $z loop!
H_ORG:
for my $h_org (@hsporg) {
for my $orginfo (@k_org) {
if($h_org eq $orginfo) {
print_index()
next H_ORG;
} elsif($h_org =~ /(\w+\s\w+)\s/ and $orginfo eq $1) {
print_zero();
next H_ORG;
}
}
print_zero();
}
これにより、一致しないことがすでにわかっているデータのチェックを回避できます。