Perl +递归子例程+访问在子例程之外定义的变量

时间:2015-03-02 08:50:44

标签: perl recursion

我正在使用Perl来提取bitbucket repo列表。来自bitbucket的响应将只包含10个存储库和下一页的标记,其中将有另外10个存储库等等...(它们称之为分页响应)

所以,我编写了一个递归子程序,如果存在下一个页面标记,它会调用自身。这将一直持续到最后一页。

这是我的代码:

#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
use LWP::UserAgent;
use JSON;

my @array;

recursive("my_bitbucket_url");
foreach ( @array ) { print $_."\n"; }

sub recursive
{
    my $url    = $_[0];

    ### here goes my LWP::UserAgent code which connects to bitbucket and pulls back the response in a JSON as $response->decoded_content 
    ### hence, removing this code for brevity

    my $hash = decode_json $response->decoded_content;
    #print Dumper ($hash);

    foreach my $a ( @{$hash->{values}} )
    {
        push @array, $a->{links}->{self}->{href};
    }

    if ( defined $hash->{next})
    {
        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";
        recursive( $hash->{next} );
    }
    else
    {
        print "Last page reached. No more recursion \n"
    }
}

现在,我的代码工作正常,它列出了所有的回购。

问题: 我不确定我使用上面的变量 my @array; 的方式。我已经在子例程之外定义了它,但是,我直接从子例程访问它。不知何故,我觉得这不对。

那么,在这种情况下如何使用递归子例程附加到数组。我的代码是否遵循Perl伦理,或者它是否真的荒谬(但是正确,因为它有效)?

更新

根据@ikegami,@ Sobrique和@Hynek -Pichi- Vychodil的建议,我得到了下面的代码,它使用了while循环并避免了recusrsion。

这是我的思考过程:

  1. 定义数组@array
  2. 使用初始bitbucket URL调用子例程call_url,并将响应保存在$hash
  3. 检查$hash以查看 next 页面标记
    • 如果 next 页面标记存在,则将元素推送到@array并使用新标记调用call_url。这将通过while循环完成。
    • 如果 next 页面标记存在 NOT ,则将元素推送到@array。周期。
  4. 打印@array内容。
  5. 这是我的代码:

    my @array;
    my $hash = call_url("my_bitbucket_url ");
    
    if (defined $hash->{next})
    {
        while (defined $hash->{next})
        {
            foreach my $a ( @{$hash->{values}} )
            {
                push @array, $a->{links}->{self}->{href};
            }
            $hash = call_url($hash->{next});
        }
    }
    
    foreach my $a ( @{$hash->{values}} )
    {
        push @array, $a->{links}->{self}->{href};
    }
    
    foreach (@array) { print $_."\n"; }
    
    sub call_url
    {
        ### here goes my LWP::UserAgent code which connects to bitbucket and pulls back the response in a JSON as $response->decoded_content 
        ### hence, removing this code for brevity    
    
        my $hash = decode_json $response->decoded_content;
        #print Dumper ($hash);
    
        return $hash;
    }
    

    肯定想知道这看起来不错还是还有改进的余地。

3 个答案:

答案 0 :(得分:3)

使用全局变量返回值演示high coupling,这是值得避免的。

您问以下是否可以接受:

my $sum;
sum(4, 5);
print("$sum\n");
sub sum {
   my ($x, $y) = @_;
   $sum = $x + $y;
}

sub是递归的事实完全无关紧要;它只是让你的榜样更大。


问题已解决:

sub recursive
{
    my $url = $_[0];

    my @array;

    my $hash = ...;

    foreach my $a ( @{$hash->{values}} )
    {
        push @array, $a->{links}->{self}->{href};
    }

    if ( defined $hash->{next})
    {
        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";
        push @array, recursive( $hash->{next} );
    }
    else
    {
        print "Last page reached. No more recursion \n"
    }

    return @array;
}

{
    my @array = recursive("my_bitbucket_url");
    foreach ( @array ) { print $_."\n"; }
}

删除了递归:

sub recursive
{
    my $url = $_[0];

    my @array;
    while (defined($url)) {    
        my $hash = ...;

        foreach my $a ( @{$hash->{values}} )
        {
            push @array, $a->{links}->{self}->{href};
        }

        $url = $hash->{next};

        if ( defined $url)
        {
            print "Next page Exists \n";
            print "Recursing with $url\n";
        }
        else
        {
            print "Last page reached. No more recursion \n"
        }
    }

    return @array;
}

{
    my @array = recursive("my_bitbucket_url");
    foreach ( @array ) { print $_."\n"; }
}

清理您发布的最新代码:

my $url = "my_bitbucket_url";

my @array;
while ($url) {
    my $hash = call_url($url);

    for my $value ( @{ $hash->{values} } ) {
       push @array, $value->{links}{self}{href};
    }

    $url = $hash->{next};
}

print("$_\n") for @array;

答案 1 :(得分:2)

是的,使用全局变量是一个坏习惯,即使它是词法范围变量。

每个递归代码都可以重写为命令式循环版本,反之亦然。这是因为所有这些都是在CPU上实现的,根本不知道有关递归的任何信息。只是跳跃。所有的调用和返回只是通过一些堆栈操作跳转,因此您可以将递归算法重写为循环。如果它不是那么明显和简单,在这种情况下,您甚至可以模拟堆栈和行为,就像在您喜欢的语言解释器或编译器中完成一样。在这种情况下,它非常简单:

my @array = with_loop("my_bitbucket_url");
foreach ( @array ) { print $_."\n"; }

sub with_loop
{
    my $url    = $_[0];
    my @array;
    while(1) 
    {

    ### here goes my LWP::UserAgent code which connects to bitbucket and 
    ### pulls back the response in a JSON as $response->decoded_content 
    ### hence, removing this code for brevity

        my $hash = decode_json $response->decoded_content;
        #print Dumper ($hash);

        foreach my $a ( @{$hash->{values}} )
        {
            push @array, $a->{links}->{self}->{href};
        }

        unless ( defined $hash->{next})
        {
            print "Last page reached. No more recursion \n";
            last
        };

        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";
        $url = $hash->{next};
    };
    return @array;
}

但是当你想坚持递归时,你可以,但它有点棘手。首先,没有尾部调用优化,因此您不必像原始版本那样尝试编写尾部调用代码。所以你可以这样做:

my @array = recursion("my_bitbucket_url");
foreach ( @array ) { print $_."\n"; }

sub recursion
{
    my $url    = $_[0];

    ### here goes my LWP::UserAgent code which connects to bitbucket and 
    ### pulls back the response in a JSON as $response->decoded_content 
    ### hence, removing this code for brevity

    my $hash = decode_json $response->decoded_content;
    #print Dumper ($hash);

    # this map version is same as foreach with push but more perlish
    my @array = map $_->{links}->{self}->{href}, @{$hash->{values}};

    if (defined $hash->{next})
    {
        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";
        push @array, recursive( $hash->{next} );
    }
    else
    {
        print "Last page reached. No more recursion \n"
    }
    return @array;
}

但是这个版本效率不高所以有办法在perl中编写尾调用递归版本,这有点棘手。

my @array = tail_recursive("my_bitbucket_url");
foreach ( @array ) { print $_."\n"; }

sub tail_recursive
{
    my $url    = $_[0];
    my @array;
    return tail_recursive_inner($url, \@array); 
    # url is mutable parameter
}

sub tail_recursive_inner
{
    my $url = $_[0];
    my $array = $_[1]; 
    # $array is reference to accumulator @array 
    # from tail_recursive function

   ### here goes my LWP::UserAgent code which connects to bitbucket and 
   ### pulls back the response in a JSON as $response->decoded_content 
   ### hence, removing this code for brevity

    my $hash = decode_json $response->decoded_content;
    #print Dumper ($hash);

    foreach my $a ( @{$hash->{values}} )
    {
        push @$array, $a->{links}->{self}->{href};
    }

    if (defined $hash->{next})
    {
        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";

        # first parameter is mutable so its OK to assign
        $_[0] = $hash->{next};
        goto &tail_recursive_inner;
    }
    else
    {
        print "Last page reached. No more recursion \n"
    }
    return @$array;
}

如果你对一些真正的perl技巧感兴趣

print $_."\n" for tricky_tail_recursion("my_bitbucket_url");

sub tricky_tail_recursion {
    my $url = shift;

   ### here goes my LWP::UserAgent code which connects to bitbucket and 
   ### pulls back the response in a JSON as $response->decoded_content 
   ### hence, removing this code for brevity

    my $hash = decode_json $response->decoded_content;
    #print Dumper ($hash);

    push @_, $_->{links}->{self}->{href} for @{$hash->{values}}; 

    if (defined $hash->{next}) {
        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";
        unshift @_, $hash->{next};
        goto &tricky_tail_recursion;
    } else {
        print "Last page reached. No more recursion \n"
    };
    return @_;
}

另请参阅:LWP::UserAgent docs。

答案 2 :(得分:1)

整个程序可以使用在任何闭包之外定义的变量。它工作正常,没有什么可担心的。有些人可能称之为“糟糕的风格”。在某些情况下(主要是围绕节目长度和距离动作),但这不是一个严格的约束。

我不确定我是否一定会看到这里递归的优势 - 你的问题似乎似乎无法保证。这本身并不是问题,但对于未来的维护程序员来说可能有点混乱;)。

我正在思考(非递归)的事情:

my $url = "my_bitbucket_url";

while ( defined $url ) {
    ##LWP Stuff;

    my $hash = decode_json $response->decoded_content;

    foreach my $element ( @{ $hash->{values} } ) {
        print join( "\n", @{ $element->{links}->{self}->{href} } ), "\n";
    }

    $url = $hash->{next}; #undef if it doesn't exist, so loop breaks. 
}