【问题标题】:Trying to print one element of an array somehow prints all elements尝试以某种方式打印数组的一个元素会打印所有元素
【发布时间】:2017-07-12 06:14:06
【问题描述】:

我正在编写一个脚本来从文件aryData 中获取整数列表,对它们进行排序,打印排序后的数组、最大值和最小值。

aryData

89 62 11 75 8 33 95 4

但在打印最高或最低值时,会打印数组的所有元素。

这是我的 Perl 代码

#!/bin/perl

use strict;
use warnings;

print "Enter filename to be sorted: ";
my $filename = <STDIN>;
chomp( $filename );

open( INFILE, "<$filename" );
my @nums = <INFILE>;
close INFILE;

my @sorted = sort { $a cmp $b } @nums;

open my $outfile, '>', "HighLow.txt";

print $outfile "Sorted numbers: @sorted";
print $outfile "Smallest number: $sorted[0] \n";
print $outfile "Largest number: $sorted[-1] \n";

输出HighLow.txt

Sorted numbers: 89 62 11 75 8 33 95 4
Smallest number: 89 62 11 75 8 33 95 4
Largest number: 89 62 11 75 8 33 95 4

【问题讨论】:

  • 如果所有数字都在文件中的一行上,您需要split 他们。您的数组只有一项。您还可以看到,因为它们没有在您的第一行输出中排序。
  • 所有数字都保存在单个数组索引中,您必须将每个元素存储在一个数组索引中,为此使用split,就像@simbabque 建议的那样
  • 这对我来说似乎是一个家庭作业问题
  • 比较数字是通过使用&lt;=&gt;而不是cmp来完成的。
  • 家庭作业很好。它不应该是“这是我的任务,我需要一个解决方案”。你做的一切都是正确的。我会给你写一个答案,解释当我在电脑前如何调试这个问题。欢迎使用 Stack Overflow 和 Perl! :-)

标签: arrays perl


【解决方案1】:

这个答案将有很大一部分代码审查和解释与问题不直接相关的概念。

让我们看看你的代码中读取数组的部分。

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 operatorASCII 字符号排序。这也是 Perl 的 sort 的默认行为,因此您可以省略整个 { $a cmp $b } 块。和刚才说sort @nums一样。

但是我们想按数值对数字进行排序,所以需要使用&lt;=&gt;排序运算符。

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。这也不是一个好主意。现在你有&lt; 模式,但是如果你把它去掉并做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;

【讨论】:

  • @DaveCross 看起来像我做过一次 :)
【解决方案2】:

正如您从 cmets 中看到的,这里的问题是您没有正确填充数组。您最终在 @nums 中只有一个元素 - 它是包含所有数据的单个元素。

您可以通过使用 Data::Dumper 之类的东西来确认这一点,它...呃...转储您的数据 :-)

在您的程序顶部,就在use warnings; 之后,您可以添加以下内容:

use Data::Dumper;

那么在你加载完@nums之后,尝试转储它:

print Dumper(\@nums), "\n";

你会看到这个:

$VAR1 = [
          '89 62 11 75 8 33 95 4
'
        ];

将其与我们解决您的问题时看到的情况进行比较,您会发现明显不同。

所以我们有一行数据,其中包含您感兴趣的数字,用空格分隔。要将其转换为我们可以存储在您的数组中的数字列表,我们可以使用split() 函数。 split() 接受两个参数 - 一个用于拆分字符串的正则表达式,以及一个要拆分的字符串。

您可以从文件中读取此代码并将其分配给您的数组:

my @nums = <INFILE>;

您可以将其替换为:

my @nums = split / /, <INFILE>;

现在我们的数据转储看起来像这样:

$VAR1 = [
          '89',
          '62',
          '11',
          '75',
          '8',
          '33',
          '95',
          '4
'
        ];

我希望区别很明显。您的程序此时基本上可以运行,但是我们可以通过处理文件中记录末尾的换行符来稍微清理一下(您可以在上面的4 之后看到它)。

我们需要将这条线分成两部分。

chomp(my $input = <INFILE>);
my @nums = split / /, $input;

现在我们的数据转储看起来像这样:

$VAR1 = [
          '89',
          '62',
          '11',
          '75',
          '8',
          '33',
          '95',
          '4'
        ];

此时,您的程序中仍然存在错误。我将把它留给你调查(提示:sort() 实际上是做什么的?Read the documentation) - 如果你有更多问题,请再问一个问题。

但我想最后建议对您的一般编码风格进行一些改进。我不确定你从哪里学习 Perl,但你做的一些东西看起来很过时。

当您在 Perl 中打开文件时,您应该始终检查调用 open 的结果,并在失败时采取适当的措施。在许多情况下,终止程序是适当的操作,因此我会在您的公开声明中使用die()

open( INFILE, "<$filename" )
  or die "Can't open $filename: $!\n");

错误信息中的$! 会告诉你为什么 Perl 不能打开文件。

如今,避免“裸字文件句柄”(如您的INFILE)并将文件名与模式指示符(&gt;&lt;)分开也被认为是最佳实践。综上所述,您的文件处理代码变为:

open( my $in_fh, '<', $filename )
  or die "Can't open $filename: $!\n";
chomp(my $input = <$in_fh>);
my @nums = split / /, $input;
close $in_fh;

我看到您已经将这种样式用于输出文件。在同一个程序中混合样式似乎很奇怪。

【讨论】:

  • 然后我们实际上有几乎相同的内容。甚至代码都是一样的。对 TIMTOWTDI 来说就这么多。 ;)
【解决方案3】:

也许你可以试试这个来找出最大值和最小值:

@a=qw(1 3 2 8 7 5 4 10 9);
@a=sort {$a<=>$b}@a;
print "the max number=$a[0]\nthe min number=$a[$#a]\n";

【讨论】:

  • 请在代码示例中使用严格和警告,并提供在它们下工作的代码。 OP已经知道如何获得最小值和最大值。您对排序运算符是正确的,但您没有解释原因。请尝试改进您的答案。你给出的是答案,而不是猜测。 ;-)
  • @Nagaraju 数字的来源并不重要。您在编程中的第一堂课是学习如何进行抽象。把每一件事都当作一个单独的问题。您已经知道如何读取文件。因此,这里不包括这实际上并不重要。相反,他们只是创建了一个数组。这是关于概念,而不是确切的代码。别担心,你会及时到达那里的。
  • 是的,很好@simbabque,不是什么大问题
猜你喜欢
  • 2013-05-06
  • 1970-01-01
  • 1970-01-01
  • 2015-06-28
  • 1970-01-01
  • 2016-01-28
  • 2011-03-23
  • 2021-10-01
  • 1970-01-01
相关资源
最近更新 更多