这个答案将有很大一部分代码审查和解释与问题不直接相关的概念。
让我们看看你的代码中读取数组的部分。
open(INFILE, "<$filename");
my @nums = <INFILE>;
close INFILE;
此代码适合您的工作,但它有一些安全和样式问题,我将在下面进一步讨论。
所以你有一个文件名,你可以逐行读入一个文件。每行进入数组@nums 中的一个元素。由于您的东西没有按照您想要的方式工作,因此您需要进行调试的第一步是尝试查看数组。
您尝试这样做并不是一个坏主意。
print "Sorted numbers: @sorted";
Interpolating Perl 中双引号"" 字符串中的数组将数组元素与变量$, 连接起来,该变量也称为output field separator。默认为空格。
my @foo = (1, 2, 3);
print "@foo";
这将给出以下输出
1 2 3
不幸的是,您的输入文件已经有空格作为分隔符,并且所有数字都在一行上。所以你看不到阵列没有正确设置。那是您自己注意到的那些面部护理时刻之一。通过查看排序后的数字,您可能已经注意到。您确实对它们进行了排序,但它们没有被排序。
Sorted numbers: 89 62 11 75 8 33 95 4
找出数组中内容的更好方法是使用Data::Dumper,它可以让您使用serialize 数据结构。它包含在 Perl 中。
use Data::Dumper;
my @foo = (1, 2, 3);
print Dumper \@foo;
该模块为您提供Dumper 函数。它喜欢在引用上效果更好,因此您需要添加反斜杠来创建对@foo 的引用。在这一点上,这究竟意味着什么并不重要。请记住,如果您的变量没有$,则在前面添加一个反斜杠。
$VAR1 = [
1,
2,
3
];
这很有用。它告诉我们三个要素。现在让我们看看你的代码。我使用的是伪文件句柄DATA,而不是实际文件,它从程序末尾的__DATA__ 部分读取。这非常适合测试和示例。
use Data::Dumper;
my @nums = <DATA>;
my @sorted = sort { $a cmp $b } @nums;
print Dumper \@sorted;
__DATA__
89 62 11 75 8 33 95 4
打印出来
$VAR1 = [
'89 62 11 75 8 33 95 4
'
];
我们可以在这里看到两件事。首先,所有数字都在一行上,因此它们进入了第一个元素。其次,该行末尾有一个换行符。您已经知道可以使用 chomp 删除它。
所以让我们尝试解决这个问题。我们现在知道我们需要split 这行数字。 There are many different ways 来完成这个任务。我会用一个非常冗长的来解释所涉及的步骤。
use Data::Dumper;
my $line = <DATA>; # only read one line
chomp $line; # remove the line ending
my @nums = split / /, $line;
my @sorted = sort { $a cmp $b } @nums;
print Dumper \@sorted;
__DATA__
89 62 11 75 8 33 95 4
我们使用split 和一个空的模式 / / 将数字字符串转换为数字列表,并将其放入数组中。然后我们排序。
$VAR1 = [
'11',
'33',
'4',
'62',
'75',
'8',
'89',
'95'
];
如您所见,我们现在有一个排序的数字列表。但它们没有按数字排序。相反,它们是sorted asciibetically。那是因为cmp is the operator 按ASCII 字符号排序。这也是 Perl 的 sort 的默认行为,因此您可以省略整个 { $a cmp $b } 块。和刚才说sort @nums一样。
但是我们想按数值对数字进行排序,所以需要使用<=>排序运算符。
use Data::Dumper;
my $line = <DATA>; # only read one line
chomp $line; # remove the line ending
my @nums = split / /, $line;
my @sorted = sort { $a <=> $b } @nums;
print Dumper \@sorted;
__DATA__
89 62 11 75 8 33 95 4
现在程序打印出正确的输出。
$VAR1 = [
'4',
'8',
'11',
'33',
'62',
'75',
'89',
'95'
];
我会让你把它放回你的实际程序中。
最后,谈谈你的open。您正在使用所谓的 glob 文件句柄。像INFILE 这样的东西是全局标识符。它们在整个程序中都有效,即使在您可能加载的其他模块中也是如此。虽然在这个并没有真正产生影响的小程序中,它可能会在未来引起问题。例如,如果 Data::Dumper 模块要打开一个文件并使用相同的标识符 INFILE,而您没有调用 close INFILE,您的程序可能会崩溃或做一些非常奇怪的事情,因为它会重用相同的句柄.
相反,您可以使用 lexical 文件句柄。词法变量仅在特定范围内有效,例如函数或循环体。它只是一个常规变量,用my 声明。当它超出范围时,它会自动为您调用close。
open my $fh, "<foo";
my @nums = <$fh>;
close $fh;
您使用两个参数调用open。这也不是一个好主意。现在你有< 模式,但是如果你把它去掉并做open my $fh, "$file" 并从用户那里读取$file,他们可能会传递一些不好的东西,比如| rm -rf slash。然后 Perl 会将管道 | 视为模式,打开管道并删除所有内容。而是使用三参数 open。
open my $fh, '<', 'foo';
既然您明确设置了模式,那么您就安全了。
最后一点是你应该经常检查open 是否有效。这很容易。
open my $fh, '<', 'foo' or die $!;
variable $! 包含open 遇到的错误。 or 只会在 open 调用的返回值为 false 时触发。而die 使程序终止。您可能收到的错误可能如下所示。
/home/foo/code/scratch.pl 第 6154 行没有这样的文件或目录。
所以完整的文件读取应该是这样的。
open my $fh, '<', $filename or die "Could not read $filename: $!";
my @nums = <$fh>;
close $fh;