在 Perl 中对 @_ 数组使用移位函数的问题

Problems using shift function on @_ array in Perl

希望在函数内部的 @_ 数组上使用 shift 函数,以便函数可以接受可变数量的参数。根据 L Wall 等人在“Programming Perl”中的 p112,传递给子例程的参数被连接成一个平面数组,即 @_。假设可以使用 shift 函数或 foreach 循环遍历此数组以确定所有传递的值中的最小值,即使某些参数本身是数组并且值包含为这些数组中的元素。

尝试了多种变体,使用“for each”循环(最接近“Programming Perl”的第 113 页上的循环)遍历 @_ 数组的 min2 和 min4 变体起作用了。使用“shift”函数遍历@_ 数组的 min1 和 min3 变体确实工作正常。关于导致基于“shift”功能的变体失败的任何想法?

四个“min”子程序如下:

# Using perl5.14.2.exe 

use strict;

sub min {
    my $m=shift @_;
    my $b;
    while ($b = shift @_){
      if ($b < $m) {$m=$b};
    };
    return $m;
};


sub min_v2 {
    my $m = $_[0];
    foreach (@_){
      if ($_ < $m) {$m = $_};
    };
    return $m;
};


sub min_v3 {
    my $m=shift (@_);
    my $b;
    while ($b = shift (@_)){
      if ($b < $m) {$m=$b};
    };
    return $m;
};

sub min_v4 {
#   Similar to Programming Perl 2nd Ed by Larry Wall et al p113
    my $m=shift (@_);
    foreach $b (@_){
      if ($b < $m) {$m=$b};
    };
    return $m;
};

以下代码用于测试例程:

print 'min 10,50,5,1 = ' ,     min (10,50,5,1 ),     "\n" ;
print 'min ((10,50,5),1) = ' , min ((10,50,5),1) ,   "\n" ;
print 'min ((10,50,0),1) = ' , min ((10,50,0),1) ,   "\n" ;

print 'min (30,0) = ',         min (30,0),           "\n";
print 'min_v2 (30,0) = ',      min_v2 (30,0),        "\n";
print 'min_v3 (30,0) = ',      min_v3 (30,0),        "\n";
print 'min_v4 (30,0) = ',      min_v4 (30,0),        "\n";

my @a;
@a = (1,2,3,30);
print '@a = ' , "[ @a ]" , , "\n";
print 'min (@a,0) = '    , min (@a,0) , "\n";
print 'min_v2 (@a,0) = ' , min_v2 (@a,0) , "\n" ;
print 'min_v3 (@a,0) = ' , min_v3 (@a,0) , "\n" ;
print 'min_v4 (@a,0) = ' , min_v4 (@a,0) , "\n" ;

@a = (3,2,1,30);
print '@a = ' , "[ @a ]" , , "\n";
print 'min (@a,0) = '    , min (@a,0) , "\n";
print 'min_v2 (@a,0) = ' , min_v2 (@a,0) , "\n" ;
print 'min_v3 (@a,0) = ' , min_v3 (@a,0) , "\n" ;
print 'min_v4 (@a,0) = ' , min_v4 (@a,0) , "\n" ;

@a = (3,2,1,30);
print '@a = ' , "[ @a ]" , , "\n";
print 'min (@a,2) = '    , min (@a,2) , "\n";
print 'min_v2 (@a,2) = ' , min_v2 (@a,2) , "\n" ;
print 'min_v3 (@a,2) = ' , min_v3 (@a,2) , "\n" ;
print 'min_v4 (@a,2) = ' , min_v4 (@a,2) , "\n" ;

并生成以下输出:

min 10,50,5,1 = 1

min ((10,50,5),1) = 1

min ((10,50,0),1) = 10

min (30,0) = 30

min_v2 (30,0) = 0

min_v3 (30,0) = 30

min_v4 (30,0) = 0

@a = [ 1 2 3 30 ]

min (@a,0) = 1

min_v2 (@a,0) = 0

min_v3 (@a,0) = 1

min_v4 (@a,0) = 0

@a = [ 3 2 1 30 ]

min (@a,0) = 1

min_v2 (@a,0) = 0

min_v3 (@a,0) = 1

min_v4 (@a,0) = 0

@a = [ 3 2 1 30 ]

min (@a,2) = 1

min_v2 (@a,2) = 1

min_v3 (@a,2) = 1

min_v4 (@a,2) = 1

此问题与 shift@_ 无关。问题是,当您从列表中移动值 0 时,while 条件计算为 false,并且循环在您预期之前终止。考虑这个更简单的例子:

use warnings;
use strict;

my @nums = (50, 30, 0, 1);
my $m = 10;
my $b;
while ($b = shift @nums) {
    print "b=$b m=$m\n";
    if ($b < $m) {$m=$b}
}
print "m=$m\n";

输出:

b=50 m=10
b=30 m=10
m=10

数组 (50, 30) 中的前 2 个值按预期运行。一旦将 0 从数组中移出,循环就会结束。

已使用@ikegami 推荐的方法更正了@toolic 识别的“基于移位”的最小函数(与基于“for-each”的函数相反)中的错误:

sub minb {
    my $m=shift @_;
    my $b;

#   Problem identified by toolic:-    
#   Below while loop exits prematurely if an element of @_ is zero
#   while ($b = shift @_){  

#   Suggestion by ikegami:-
#   Below while loop continues until its length @_ is reduced to zero
    while (@_) {   # i.e length @_ > 0   
      $b = shift @_;

      if ($b < $m) {$m=$b};
    };
    return $m;

请注意,将此作为答案而不是评论发布的原因是似乎无法在评论块中正确格式化代码。