我目前正在编写一个perl脚本,其中我引用了一个数组(学生)的引用。将哈希引用添加到数组后。现在我添加对学生数组的引用,然后询问用户如何对它们进行排序。这是令人困惑的地方。我不知道如何顺序排序的数组。使用dumper我可以得到排序的数组,但是在一个无组织的输出中。 如何在排序后对哈希引用数组进行处理?
#!bin/usr/perl
use strict;
use warnings;
use Data::Dumper;
use 5.010;
#reference to a var $r = \$var; Deferencing $$r
#reference to an array $r = \@var ; Deferencing @$r
#referenc to a hash $r = \%var ; deferencing %$r
my $filename = $ARGV[0];
my $students = [];
open ( INPUT_FILE , '<', "$filename" ) or die "Could not open to read \n ";
sub readLines{
while(my $currentLine = <INPUT_FILE>){
chomp($currentLine);
my @myLine = split(/\s+/,$currentLine);
my %temphash = (
name => "$myLine[0]",
age => "$myLine[1]",
GPA => "$myLine[2]",
MA => "$myLine[3]"
);
pushToStudents(\%temphash);
}
}
sub pushToStudents{
my $data = shift;
push $students ,$data;
}
sub printData{
my $COMMAND = shift;
if($COMMAND eq "sort up"){
my @sortup = sort{ $a->{name} cmp $b->{name} } @$students;
print Dumper @sortup;
}elsif($COMMAND eq "sort down"){
my @sortdown = sort{ $b->{name} cmp $a->{name} } @$students;
print Dumper @sortdown;
//find a way to deference so to make a more organize user friendly read.
}else{
print "\n quit";
}
}
readLines();
#Output in random, the ordering of each users data is random
printf"please choose display order : ";
my $response = <STDIN>;
chomp $response;
printData($response);
答案 0 :(得分:2)
这里的问题是您希望Dumper
提供有组织的输出。它没有。它转储数据结构以使调试更容易。关键问题是哈希是明确无序的数据结构 - 它们是键值映射,它们不产生任何输出顺序。
参考perldata
:
请注意,仅仅因为哈希按该顺序初始化并不意味着它以该顺序出现。
特别是keys
功能:
哈希条目以明显随机的顺序返回。实际的随机顺序特定于给定的哈希值;两个哈希值上完全相同的一系列操作可能会导致每个哈希值的顺序不同。
perlsec
中有一整节更详细地解释了这一点,但足以说 - 哈希是随机顺序,这意味着当您按照学生排序时名称,每个学生的键值对不是排序的。
我建议而不是:
my @sortdown = sort{ $b->{name} cmp $a->{name} } @$students;
print Dumper @sortdown;
使用slice
:
my @field_order = qw ( name age GPA MA );
foreach my $student ( sort { $b -> {name} cmp $a -> {name} } @$students ) {
print @{$student}{@field_order}, "\n";
}
数组(@field_order
)是明确排序的,因此您将始终以相同的顺序打印学生字段。 (Haven没有对你的例子进行全面测试我很害怕,因为我没有你的源数据,但这种方法适用于样本数据片段。)
如果您确实需要打印键,那么您可能需要使用foreach循环:
foreach my $field ( @field_order ) {
print "$field => ", $student->{$field},"\n";
}
或许更简洁:
print "$_ => ", $student -> {$_},"\n" for @field_order;
我不确定我喜欢那个,但这可能是品味的问题。
答案 1 :(得分:2)
你的错误的本质是假设哈希会有一个特定的顺序。作为@Sobrique explains,这种假设是错误的。
我假设您正在尝试学习Perl,因此,有关基础知识的一些指导将非常有用:
#!bin/usr/perl
您的shebang line错误:在Windows上,或者如果您使用perl script.pl
运行脚本,它无关紧要,但您要确保该行中指定的解释器使用绝对值路径。
此外,您可能并不总是希望使用系统附带的perl
解释器,在这种情况下#!/usr/bin/env perl
可能对一次性脚本有用。
use strict; use warnings; use Data::Dumper; use 5.010;
我倾向于在pragmata之前更喜欢版本约束(utf8
除外)。 Data::Dumper
是一种调试辅助工具,而不是用于人类可读报告的工具。
my $filename = $ARGV[0];
您应该检查您是否确实在命令行上给出了参数,如:
@ARGV or die "Need filename\n";
my $filename = $ARGV[0];
打开(INPUT_FILE,'&lt;',“$ filename”)或死“无法打开阅读\ n”;
INPUT_FILE
等文件句柄称为 bareword 文件句柄。这些都有包装范围。相反,使用词法文件句柄,其范围可以限制为最小的适当块。
无需在$filename
的第三个参数中插入open
。
在open
中的错误消失时,始终包含文件名和错误消息。使用' '
围绕文件名可以帮助您识别可能导致问题的任何其他难以检测的字符(例如换行符或空格)。
open my $input_fh, '<', $filename
or die "Could not open '$filename' for reading: $!";
sub readLines{
这是读入您在全局范围内定义的数组。如果要使用相同的子例程将两个不同文件中的记录读入两个单独的数组,该怎么办? readLines
应该接收一个文件名作为参数,并返回一个arrayref作为其输出(见下文)。
while(my $currentLine = <INPUT_FILE>){ chomp($currentLine);
在大多数情况下,您希望删除所有尾随空格,而不仅仅是行终止符。
my @myLine = split(/\s+/,$currentLine);
/\s+/
上的 split
与split ' '
不同。在大多数情况下,后者更有用。阅读perldoc -f split
。
my %temphash = ( name => "$myLine[0]", age => "$myLine[1]", GPA => "$myLine[2]", MA => "$myLine[3]" );
再次使用无用插值。没有必要将这些值插入到新的字符串中(除非它们可能是重载字符串的对象,但在这种情况下,您知道它们只是简单的字符串。
pushToStudents(\%temphash);
在这种情况下不需要额外的pushToStudents
子例程,除非这是一个方法的存根,以后可以将数据加载到数据库或其他东西。即使在这种情况下,最好还是为函数提供回调。
sub pushToStudents { 我的$ data = shift; 推送$ students,$ data; }
您正在将数据推送到全局变量。一个只能有一个学生记录阵列的程序是没用的。
sub printData{ my $COMMAND = shift; if($COMMAND eq "sort up"){
不要这样做。每个子程序都应该有一个明确的目的。
以下是您的计划的修订版。
#!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
use Carp qw( croak );
run(\@ARGV);
sub run {
my $argv = $_[0];
@$argv
or die "Need name of student records file\n";
open my $input_fh, '<', $argv->[0]
or croak "Cannot open '$argv->[0]' for reading: $!";
print_records(
read_student_records($input_fh),
prompt_sort_order(),
);
return;
}
sub read_student_records {
my $fh = shift;
my @records;
while (my $line = <$fh>) {
last unless $line =~ /\S/;
my @fields = split ' ', $line;
push @records, {
name => $fields[0],
age => $fields[1],
gpa => $fields[2],
ma => $fields[3],
};
}
return \@records;
}
sub print_records {
my $records = shift;
my $sorter = shift;
if ($sorter) {
$records = [ sort $sorter @$records ];
}
say "@{ $_ }{ qw( age name gpa ma )}" for @$records;
return;
}
sub prompt_sort_order {
my @sorters = (
[ "Input order", undef ],
[ "by name in ascending order", sub { $a->{name} cmp $b->{name} } ],
[ "by name in descending order", sub { $b->{name} cmp $a->{name} } ],
[ "by GPA in ascending order", sub { $a->{gpa} <=> $b->{gpa} } ],
[ "by GPA in descending order", sub { $b->{gpa} <=> $a->{gpa} } ],
);
while (1) {
print "Please choose the order in which you want to print the records\n";
print "[ $_ ] $sorters[$_ - 1][0]\n" for 1 .. @sorters;
printf "\n\t(%s)\n", join('/', 1 .. @sorters);
my ($response) = (<STDIN> =~ /\A \s*? ([1-9][0-9]*?) \s+ \z/x);
if (
$response and
($response >= 1) and
($response <= @sorters)
) {
return $sorters[ $response - 1][1];
}
}
# should not be reached
return;
}