【问题标题】:How to loop through HoA whose values are arrayref?如何遍历值为arrayref的HoA?
【发布时间】:2013-03-23 05:17:21
【问题描述】:

我想让我的脚本尽可能高效和快速地运行。以下是我创建 HoA 的方法。

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

说它给出了如下的数据结构

%HoA = (
    'C1' => ['1', '3', '3', '3'],
    'C2' => ['3','2'],
    'C3' => ['1','3','3','4','5','5'],
    'C4' => ['3','3','4'],
    'C5' => ['1'],
);

现在假设对于 HoA 中的每个键,我想将它的值(数组)和整个 HoA 传递到一个名为 compute 的子例程中。

这是我目前的做法。

foreach my $key ( keys %HoA ) {
    compute($HoA{$key}, \%HoA);  # on the first iteration, this actually passes an aref to [1,3,3,3]
}

显然 $HoA{$key} 已经是该特定 $key 的每个值的数组引用。

如果是这种情况,那么执行以下操作在效率方面是否有任何优势

push( @{$HoA{$key}, \@cols );

它产生以下数据结构

%HoA = (
    'C1' => [ ['1', '3'], ['3', '3'] ],
    'C2' => [ ['3','2'] ],
    'C3' => [ ['1','3'], ['3','4'], ['5','5'] ],
    'C4' => [ ['3','3','4'] ],
    'C5' => [ ['1'] ],
);

这会让我的脚本运行得更快吗?如果是这样,在这种情况下,我如何将每个键的值(array_ref)传递给子例程?一旦它在子例程中,我如何访问数组中的各个元素而不取消引用子例程中的整个 array_ref?另外,对于$hash_ref,如何访问每个array_ref?

这是我目前拥有的方式

sub compute{
# takes one param: an arrayref
my ($array_ref, $hash_ref) = @_;
    for my $p ( @{ $array_ref) ) {
        # do stuff
    }
    for my $x ( values %{ %hash_ref)) {
        # do stuff
    }
}

【问题讨论】:

  • 为了让你的脚本运行得更快,你需要先看大局。到目前为止,您只关注过微优化。这不会太多。
  • 首先分析你的应用程序,看看慢的部分到底在哪里
  • 但是如果你想要微优化,为什么不从我对你上一个问题的回答开始。你忽略了其中三个。
  • @stevenl 我做到了。我的脚本的瓶颈(最慢的部分)是我将 %HoA 和 arrayrefs 传递到子例程,然后进行计算的地方。我已经修改了我的子例程,只使用如上所示的引用来循环每个元素。我现在想下一步是对我传递给子例程的实际数据结构做一些事情。
  • 瓶颈是需要 50 微秒的东西???

标签: arrays performance perl hash reference


【解决方案1】:

由于代码中的取消引用,这可能会使您的代码运行速度变慢。此外,您需要展平每个哈希条目指向的数组中包含的数组,如下所示:

my %h = ( a => [ [1, 2], [3, 4] ], b => [ [2, 3], [5, 6] ]); 
my $key = 'a';
my @all_items;
@all_items = map { @$_ } @{$h{$key}};

仅根据经验,在 Perl 中取消引用对象(即数组)的成本可能非常高,因此使用较少引用的第一种方法应该更快。但你实际上可以为这些事情计时。最好的办法是只编写代码,然后在将代码作为一个整体进行分析后,再考虑真正重要的部分。

【讨论】:

  • 例如,在一个紧密的循环中,它可以将运行时间增加一个常数因子。
  • 为什么?有什么区别?
  • 您可能需要澄清“$h{$key} # is a list of array refs”。即使我不明白你的意思。
  • @perreal: checkout map 在列表上下文中评估 EXPR 与 BLOCK
  • (我有些困惑,所以我删除了我的 cmets。他们是真的,但你的回答比我想象的要好。+1。这个评论很快也会自毁。)
【解决方案2】:

如果是这样的话,在效率方面做以下事情是否有任何优势

push( @{$HoA{$key}, \@cols );

没有。首先正确创建数据结构

push @{ $HoA{$key} }, @cols;    # Copies the number to the anon array.

compute($_, \%HoA) for values %HoA;

比以后重新创建正确的数据结构要好。

push @{ $HoA{$key} }, \@cols;

compute([ map @$_, @$_ ], \%HoA) for values %HoA;  # But so does this.

通过切换,您最终会完成您已经在做的工作的适当超集。


正如 cmets 中所述,您正在优化错误的东西。您说您的程序需要 30-40 秒才能运行。即使您将加载文件所需的时间减少到零,您的程序仍然需要 29-39 秒。

【讨论】:

  • 好吧,如果是这样,我该如何修改我的子程序和子程序调用以使其工作?您还在之前的评论中提到将map { @$_ } 更改为map @$_,将比删除一个取消引用节省100 倍。我应该检查我的代码并删除不必要的括号和括号吗?这真的会让我的代码运行得更快吗?
  • 哦等等,同一个键能在输入文件中出现两次吗?
  • Re: No 同一个键不能在输入文件中出现两次。考虑到我现在拥有的代码,$HoA{$key} 已经是一个 array_ref,所以如果我这样做 $HoA{$key} = \@cols; 以便在子中使用它,我是否不必取消引用它两次或至少在其中进行修改?
  • 出于某种原因,我假设密钥只能在文件中找到一次。固定。
  • 好吧,看来我不需要对我构建的数据结构进行任何更改。我的问题是你在之前的评论中提到的。你说:“将map { @$_ } 更改为map @$_,将比删除一个取消引用节省100 倍。”这真的是真的吗?我应该检查我的代码并删除不必要的括号和括号吗?这会让我的代码运行得更快吗?
猜你喜欢
  • 1970-01-01
  • 2012-08-02
  • 1970-01-01
  • 1970-01-01
  • 2012-03-08
  • 2019-12-15
  • 2017-08-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多