使用哈希引用的最佳方式是什么?

3
我正在编写一个脚本,生成多个大型数组哈希数据结构。目前脚本运行所需时间较长,我正在尝试优化它。
我进行了一些基准测试。通过利用数组引用、直接使用@_而不是将其复制到变量中以减少子例程调用开销,我已经成功将脚本执行速度提高了约3.5倍。我还删除了不必要的子例程和冗余变量声明。尽管有了这些改进,我仍然想让代码运行得更快。
在我的脚本开始时,我会遍历一个大文件,生成两个数组哈希数据结构。以下哪种哈希引用方法最可行、最有效?数组哈希的格式类似于此:
%HoA = (
    'C1' =>  ['1', '3', '3', '3'],
    'C2' => ['3','2'],
    'C3' => ['1','3','3','4','5','5'],
    'C4'  => ['3','3','4'],
    'C5' => ['1'],
);

选项 1

当我解析文件时生成哈希数组(见下文)。最后将哈希数组放入哈希引用中。

my $hash_ref = \%HoA;

选项 2

解析文件,使 HoA(哈希套数组)中的每个键都有一个指向 array_ref 的值。最后将数组的哈希放入哈希 ref 中。

==============

我觉得选项 2 是一个不错的方法,但我该怎么做呢?

这是我目前的做法。

use strict; use warnings;
open(F1, "file.txt") or die $!;
my %HoA = ();
    while (<F1>){
    $_=~ s/\r//;
    chomp;
    my @cols = split(/\t/, $_);

    push( @{$HoA{$cols[0]}}, @cols[1..$#cols]);
 }
close F1;

我需要一种高效的数据结构,能够快速查找值和键。此外,我需要能够尽可能高效地多次将键值(数组)、键和 HoA(哈希表)传递到子程序中。


3
你的例子中有一个错别字:“my %HoA_ref = {};”。你使用了错误的符号,应该是 $ - chrsblck
1
请使用标量变量代替裸字作为文件句柄。 - Krishnachandra Sharma
4个回答

4
  • 不要使用全局变量,包括文件句柄。
  • 你声明了 %HoA 却没有使用。
  • 你声明了 $HoA_ref 却没有使用它。
  • 在未声明的情况下使用了 $HoA。始终使用use strict; use warnings;
  • 为什么要创建一个不需要的引用,然后多次对其进行解引用?
  • 没有必要将空列表分配给刚刚创建的散列。my %HoA = (); 是无意义的。
  • 最好将 s///chomp 结合起来;
  • 如果不需要,可以省略 $_,或者使用有意义的变量名。

以上所有内容和其他一些改进已经得到改进:

use strict;
use warnings;

open(my $fh, '<', 'file.txt') or die $!;

my %HoA;
while (<$fh>){
    s/\r?\n\z//;
    my ($key, @cols) = split /\t/;
    push @{ $HoA{$key} }, @cols;
}

谢谢,我会尝试两种方法,看哪一种运行时间最快。我想,由于我有几个子程序使用数组和哈希引用作为参数选项2可能是一个不错的选择。我的子程序以标量形式处理哈希和数组引用。我在子程序中不解除它们的引用。 - cooldood3490
你可以在填充哈希表后创建引用。 - ikegami
我向你展示了我会使用的内容,并且几乎每一行代码,我都解释了为什么要这样做。不知道你从这些评论中想得到什么。 - ikegami
关于那个循环:可以写成 compute( @{ $h{$key} } )(哈希表)或者 compute( @{ $h->{$key} } )(哈希表引用)。整个循环可以写成 compute($_) for values %h;(哈希表)或者 compute(@$_) for values %$h;(哈希表引用)。 - ikegami
关于选项1和选项2的问题:我展示了我会使用的代码,并且几乎每一行都解释了为什么我会这样做。你还想要什么?难道你只是想重复一遍,在那段代码中绝对没有理由使用对%HoA的引用吗? - ikegami
显示剩余3条评论

2

我的经验是尽可能使用引用最好。以下是一些额外的注意事项:

  1. 如果您需要此功能以实现Windows的eol兼容性,则需要更好的Perl构建:$_=~ s/\r//;。ActiveState通常是最健壮的。 chomp 应该会处理终端的cr / lf,或者文件读取应该已经将cr / lf对转换为仅为lf。

  2. Perl shift 是O(1)并且非常快速。在这里可以利用它。

  3. 您无法预先知道哪个选项最快。基准测试是唯一的方法。

  4. 尝试仅使用不进行任何处理的输入文件。一旦作业受到I/O限制,优化就不再有帮助了。

这是我会开始的内容:

 open(F, "file.txt") or die $!;
 my $h = {};
 while (<F>){
   chomp;
   my @cols = split "\t";
   my $key = shift @cols;
   push @{$h->{$key}}, @cols;
 }
 close F;

不要假装split的第一个参数不是正则表达式。原帖中的/\t/更好。 - ikegami
整个“shift”可以通过在前一行的列表赋值中添加“$key”来避免。 - ikegami
@Gene 如果我想将一个特定键的 array_ref 传递到名为 compute 的子例程中,我该怎么做?是这样吗 foreach my $key (keys %{ $h }) { compute( %{ $h{$key} } }? - cooldood3490
还有更简单的方法吗? - cooldood3490

1
作为您的助手,我可以为您翻译以下内容:

由于您有一个大文件,建议使用 File::Slurp 模块进行完整的文件读取,而不是使用 while 循环。

File::Slurp 的 read_file 函数尝试通过 sysread 来绕过 Perl I/O(请查看 read_file source code)。

my $text = read_file( $file ) ;


1
我认为这就是您在示例中试图做的事情。
open(my $fh, "<", "file.txt") or die $!;
my $HoA_ref = {}; # ref will return a HASH 
while (my $line = <$fh>) {
    $line =~ s/\r//;
    chomp $line;
    my @cols = split(/\t/, $line);

    # shift off first element in the list to use 
    # as the key
    my $key = shift(@cols);
    # set value to an array ref of whatever 
    # is left in the list.
    $HoA_ref->{$key} => [@cols];
}
close <$fh>;

值得注意的是,在循环遍历文件时,如果$key出现多次,它将被覆盖。

我认为他使用push来避免覆盖问题,因为它总是将新项目附加到列表末尾。 - Gene
@Gene - 你可能是对的。原帖中提到了想要 key => list_ref 的数据结构。 - chrsblck
[@col] 无需复制 @col。请使用 \@cols - ikegami
整个“shift”可以通过在前一行的列表赋值中添加“$key”来避免。 - ikegami
这些和其他改进在我的回答中提到。 - ikegami

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接