遍历 Perl 数组的最佳方法

Posted

技术标签:

【中文标题】遍历 Perl 数组的最佳方法【英文标题】:Best way to iterate through a Perl array 【发布时间】:2012-05-16 06:28:39 【问题描述】:

遍历 Perl 数组的最佳实现是(就速度和内存使用而言)?有没有更好的办法? (@Array 不需要保留)。

实施1

foreach (@Array)

      SubRoutine($_);

实施 2

while($Element=shift(@Array))

      SubRoutine($Element);

实施 3

while(scalar(@Array) !=0)

      $Element=shift(@Array);
      SubRoutine($Element);

实施 4

for my $i (0 .. $#Array)

      SubRoutine($Array[$i]);

实施 5

map  SubRoutine($_)  @Array ;

【问题讨论】:

为什么会有“最佳”?特别是考虑到我们不知道您将如何衡量一个与另一个(速度比内存使用更重要?是map 和可接受的答案?等等) 你发布的三个中有两个会让我去“WTH?!”除非有额外的周围环境使它们成为明智的选择。无论如何,这个问题属于“两个数字相加最好的方法是什么?”大多数时候,只有一种方法。然后,在某些情况下,您需要一种不同的方式。投票结束。 @SinanÜnür 我赞同你的观点(只有一种方法可以将两个数字相加),但是这个类比不够强,不能轻视。显然,方法不止一种,OP 想要了解什么是好主意,什么不是。 Programming Perl 第三版的第 24 章有一节关于效率的内容值得一读。它解决了不同类型的效率,例如时间、程序员、维护者。该部分以“请注意,优化时间有时可能会降低空间或程序员效率的成本(由下面的冲突提示表示)开始。他们是休息时间。” 两个数字相加的一种方法?如果您研究较低级别的调用/实现,则不会……考虑进行前瞻,进行保存加法器等。 【参考方案1】:

在速度方面:#1 和 #4,但在大多数情况下不会太多。

您可以编写一个基准来确认,但我怀疑您会发现 #1 和 #4 会稍微快一些,因为迭代工作是在 C 而不是 Perl 中完成的,并且不会发生不必要的数组元素复制。 ($_aliased 到 #1 中的元素,但 #2 和 #3 实际上 复制 数组中的标量。)

#5 可能类似。

就内存使用而言:除了 #5 之外,它们都相同。

for (@a) 是特殊情况以避免数组变平。循环遍历数组的索引。

在可读性方面:#1。

在灵活性方面:#1/#4 和 #5。

#2 不支持错误的元素。 #2 和 #3 具有破坏性。

【讨论】:

哇,您用简短的句子添加了大量信息。 #2 在你做队列时很好(例如广度优先搜索):my @todo = $root; while (@todo) my $node = shift; ...; push @todo, ...; ...; 实现 4 是否不会创建一个中间索引数组,这可能会引入大量要使用的内存?如果是这样,听起来不应该使用这种方法。 ***.com/questions/6440723/…rt.cpan.org/Public/Bug/Display.html?id=115863 @ikegami 忠于你的冠军风格 - 很好的答案:)【参考方案2】:

如果你只关心@Array的元素,使用:

for my $el (@Array) 
# ...

如果索引很重要,请使用:

for my $i (0 .. $#Array) 
# ...

或者,从perl 5.12.1 开始,您可以使用:

while (my ($i, $el) = each @Array) 
# ...

如果你需要循环体中的元素和它的索引,我希望使用each 是最快的,但是你会放弃与 pre-5.12.1 perls 的兼容性。

在某些情况下,其他一些模式可能更合适。

【讨论】:

我希望each 是最慢的。它完成了其他人的所有工作,减去一个别名、一个列表分配、两个标量副本和两个标量清除。 而且,就我的测量能力而言,你是对的。使用for 迭代数组的索引大约快 45%,迭代数组引用的索引时快 20%(我在正文中访问 $array->[$i]),超过使用 each 和 @987654332 @.【参考方案3】:

IMO,实现#1 是典型的,对于 Perl 来说,它的简短和惯用仅在这一点上就胜过其他实现。至少,这三种选择的基准可以让您深入了解速度。

【讨论】:

【参考方案4】:

1 与 2 和 3 大不相同,因为它使数组保持完整,而其他两个则保持空。

我会说#3 很古怪,而且可能效率较低,所以算了吧。

这让你有了#1 和#2,它们做的事情不一样,所以一个不能比另一个“更好”。如果数组很大并且您不需要保留它,一般范围会处理它(但请参阅 注意),所以 一般来说,#1 仍然是最清晰和最简单的方法。关闭每个元素不会加快任何速度。即使需要从引用中释放数组,我也会去:

undef @Array;

完成后。

注意:包含数组范围的子程序实际上保留了数组并在下次重新使用空间。 一般,应该没问题(见 cmets)。

【讨论】:

@Array = (); 不会释放底层数组。即使超出范围也不会这样做。如果你想释放底层数组,你可以使用undef @Array; 演示; perl -MDevel::Peek -e'my @a; Dump(\@a,1); @a=qw( a b c ); Dump(\@a,1); @a=(); Dump(\@a,1); undef @a; Dump(\@a,1);' 2>&1 | grep ARRAY WHAT??? 我原以为 GC 的重点是一旦 ref count == 0,所涉及的内存就可以回收了。 @ikegami:我看到了关于()undef 的事情,但是如果超出范围并不会释放该范围内的本地数组使用的内存,那不是perl泄漏的灾难?这不可能是真的。 它们也不会泄漏。 sub 仍然拥有它们,并将在下次调用 sub 时重用它们。针对速度进行了优化。【参考方案5】:

决定此类问题以对其进行基准测试的最佳方法:

use strict;
use warnings;
use Benchmark qw(:all);

our @input_array = (0..1000);

my $a = sub 
    my @array = @[ @input_array ];
    my $index = 0;
    foreach my $element (@array) 
       die unless $index == $element;
       $index++;
    
;

my $b = sub 
    my @array = @[ @input_array ];
    my $index = 0;
    while (defined(my $element = shift @array)) 
       die unless $index == $element;
       $index++;
    
;

my $c = sub 
    my @array = @[ @input_array ];
    my $index = 0;
    while (scalar(@array) !=0) 
       my $element = shift(@array);
       die unless $index == $element;
       $index++;
    
;

my $d = sub 
    my @array = @[ @input_array ];
    foreach my $index (0.. $#array) 
       my $element = $array[$index];
       die unless $index == $element;
    
;

my $e = sub 
    my @array = @[ @input_array ];
    for (my $index = 0; $index <= $#array; $index++) 
       my $element = $array[$index];
       die unless $index == $element;
    
;

my $f = sub 
    my @array = @[ @input_array ];
    while (my ($index, $element) = each @array) 
       die unless $index == $element;
    
;

my $count;
timethese($count, 
   '1' => $a,
   '2' => $b,
   '3' => $c,
   '4' => $d,
   '5' => $e,
   '6' => $f,
);

并在为 x86_64-linux-gnu-thread-multi 构建的 perl 5 版本 24 subversion 1 (v5.24.1) 上运行它

我明白了:

Benchmark: running 1, 2, 3, 4, 5, 6 for at least 3 CPU seconds...
         1:  3 wallclock secs ( 3.16 usr +  0.00 sys =  3.16 CPU) @ 12560.13/s (n=39690)
         2:  3 wallclock secs ( 3.18 usr +  0.00 sys =  3.18 CPU) @ 7828.30/s (n=24894)
         3:  3 wallclock secs ( 3.23 usr +  0.00 sys =  3.23 CPU) @ 6763.47/s (n=21846)
         4:  4 wallclock secs ( 3.15 usr +  0.00 sys =  3.15 CPU) @ 9596.83/s (n=30230)
         5:  4 wallclock secs ( 3.20 usr +  0.00 sys =  3.20 CPU) @ 6826.88/s (n=21846)
         6:  3 wallclock secs ( 3.12 usr +  0.00 sys =  3.12 CPU) @ 5653.53/s (n=17639)

所以“foreach (@Array)”的速度大约是其他的两倍。其他都非常相似。

@ikegami 还指出,除了速度之外,这些实现还有很多不同。

【讨论】:

比较$index &lt; $#array实际上应该是$index &lt;= $#array,因为$#array不是数组的长度,而是它的最后一个索引。【参考方案6】:

单行打印元素或数组。

为 (@array) 打印 $_;

注意:请记住 $_ 在内部引用循环中的 @array 元素。 $_ 中所做的任何更改都将反映在@array 中; 前任。

my @array = qw( 1 2 3 );
for (@array) 
        $_ = $_ *2 ;

print "@array";

输出:2 4 6

【讨论】:

以上是关于遍历 Perl 数组的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

perl 遍历对象数组

在 React 中让前 3 个元素循环遍历数组的最佳方法?

Perl基础---遍历散列

遍历 Perl 哈希键的最安全方法是啥?

遍历数组中的元素和元素

遍历非常大的 JSON 数组