该脚本运行一个测试用例。请注意,您的预期输出在客观上是错误的:在文件 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;
都使用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 次。这是非常缓慢且不必要的,因为我们可以事先这样做。
如果条件为假,则无需在
if ($h_org[0] eq $orginfo[0]) {
...;
} elsif ($h_org[0] ne $orginfo[0]) {
...;
}
as$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
限制结果片段的数量。因为我们只对第一个字段感兴趣,所以我们也不必拆分其他字段。
接下来,我们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 次以将其打印到不同的文件中。更好:定义两个 subprint_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();
}
这样可以避免检查您已经知道不匹配的数据。