2

在听说 Perl 一年之后,我决定抽出几个小时的时间来看看我能学到多少。我很好地完成了基础知识,然后进入了循环。作为一个测试,我想看看我是否可以构建一个脚本来递归所有最多 4 个字符的字母数字值。前段时间我写了一个 PHP 代码做同样的事情,所以我采用了相同的概念并使用它。但是,当我运行脚本时,它会将“a”作为前 3 个值,然后仅循环遍历最后一个数字。有人看到我做错了吗?

#!/usr/local/bin/perl 

$chars = "abcdefghijklmnopqrstuvwxyz";
$chars .= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$chars .= "0123456789";

@charset = split(//, $chars);

$charset_length = scalar(@charset);

sub recurse
{
 ($width, $position, $base_string) = @_;

for ($i = 0; $i < $charset_length; ++$i) {
    $base = $base_string . $charset[$i];
    if ($position < $width - 1) {
        $pos = $position + 1;
        recurse($width, $pos, $base);
    }
    print $base;
    print "\n";
}
}

recurse(4, 0, '');

这是我运行它时得到的:

aaaa
aaab
aaac
aaad
aaae
aaaf
aaag
aaah
aaai
aaaj
aaak
aaal
aaam
aaan
aaao
aaap
aaaq
aaar
aaas
aaat
aaau
aaav
aaaw
aaax
aaay
aaaz
aaaA
aaaB
aaaC
aaaD
aaaE
aaaF
aaaG
aaaH
aaaI
aaaJ
aaaK
aaaL
aaaM
aaaN
aaaO
aaaP
aaaQ
aaaR
aaaS
aaaT
aaaU
aaaV
aaaW
aaaX
aaaY
aaaZ
aaa0
aaa1
aaa2
aaa3
aaa4
aaa5
aaa6
aaa7
aaa8
aaa9
aaa9
aaa9
aaa9
4

5 回答 5

12

你已经被非严格的作用域所困扰,这段代码做了它应该做的事情(注意顶部的 use strict 以及随后使用 my 来保证变量作用域)。

#!/usr/bin/env perl
use strict;
use warnings;

my $chars = "abcdefghijklmnopqrstuvwxyz";
$chars .= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$chars .= "0123456789";

my @charset = split(//, $chars);

my $charset_length = scalar(@charset);

sub recurse {
    my ($width, $position, $base_string) = @_;

    for (my $i = 0; $i < $charset_length; ++$i) {
        my $base = $base_string . $charset[$i];

        if ($position < $width - 1) {
            my $pos = $position + 1;
            recurse($width, $pos, $base);
        }

        print $base;
        print "\n";
    }
}

recurse(4, 0, '');
于 2011-07-25T17:25:20.413 回答
10

已经得到很好的回答,但更惯用的方法是:

use strict;
use warnings;

sub recurse {
    my ($width, $base_string, $charset) = @_;

    if (length $base_string) {
        print "$base_string\n";
    }
    if (length($base_string) < $width) {
        $recurser->($base_string . $_) for @$charset;
    }
}

my @charset = ('a'..'z', 'A'..'Z', '0'..'9');
recurse(4, '', \@charset);

无需通过位置;它隐含在传入的基本字符串的宽度中。另一方面,应该传入字符集,而不是让子例程使用外部变量。

或者,由于宽度和字符集保持不变,生成一个引用它们的闭包:

use strict;
use warnings;

sub make_recurser {
    my ($width, $charset) = @_;
    my $recurser;
    $recurser = sub {
        my ($base_string) = @_;

        if (length $base_string) {
            print "$base_string\n";
        }
        if (length($base_string) < $width) {
            $recurser->($base_string . $_) for @$charset;
        }
    }
}

my @charset = ('a'..'z', 'A'..'Z', '0'..'9');
my $recurser = make_recurser(4, \@charset);
$recurser->('');

或者,只需:

print "$_\n" for glob(('{' . join(',', 'a'..'z', 'A'..'Z', '0'..'9') . '}') x 4);
于 2011-07-25T18:05:29.997 回答
4

它与变量的范围有关,当您调用递归时,您仍在更改相同的变量。关键字“my”声明了子程序本地的变量。(http://perl.plover.com/FAQs/Namespaces.html)

我总是将 perl 与 'use strict;' 一起使用 声明,迫使我决定变量的范围。

sub recurse {
  my ($width, $position, $base_string) = @_;
  for (my $i = 0; $i < $charset_length; ++$i) {
    my $base = $base_string . $charset[$i];
    if ($position < $width - 1) {
      my $pos = $position + 1;
      recurse($width, $pos, $base);
    }
    print $base;
    print " ";
  }
}
于 2011-07-25T17:25:47.117 回答
4

您似乎遇到了一些范围界定问题。Perl 非常灵活,所以它会猜测你想要什么,因为你还没有告诉它你想要什么。您将学习的第一件事是use strict;在 shebang 之后添加您的第一个语句。它将指出未明确定义的变量,以及在创建之前访问的任何变量(帮助拼写错误的变量等)。

如果你让你的代码看起来像这样,你就会明白为什么会出现错误:

sub recurse {
    ($width, $position, $base_string) = @_;

    for ($i = 0; $i < $charset_length; ++$i) {
        $base = $base_string . $charset[$i];
        if ($position < $width - 1) {
            $pos = $position + 1;
            recurse($width, $pos, $base);
        }
        # print "$base\n";
    }
    print "$position\n";
}

这应该输出:

3
3
3
3

因为您没有$position正确确定范围my,所以每次递归都没有得到一个新变量,而是在重复使用同一个变量。在那里扔一个use strict;,并修复你得到的错误,代码应该是好的。

于 2011-07-25T17:29:47.763 回答
2

I realize that you're just tinkering with recursion. But as long as you're having fun comparing implementations between two languages you may as well also see how the CPAN can extend your tool set.

If you don't care about the order, you can generate all 13,388,280 permutations of ( 'a'..'z', 'A..'Z', '0'..'9' ) taken four at a time with the CPAN module, Algorithm::Permute

Here is an example of how that code may look.

use strict;
use warnings;
use Algorithm::Permute;

my $p = Algorithm::Permute->new( 
    [ 'a' .. 'z', 'A' .. 'Z', '0' .. '9' ], # Set of...
    4 # <---- at a time.
);

while ( my @res = $p->next ) {
    print @res, "\n";
}

The new() method accepts an array ref that enumerates the character set or list of what to permute. Its second argument is how many at a time to include in the permutation. So you're essentially taking 62 items 4 at a time. Then use the next() method to iterate through the permutations. The rest is just window dressing.

The same thing could be reduced to the following Perl one-liner:

perl -MAlgorithm::Permute -e '$p=Algorithm::Permute->new(["a".."z","A".."Z",0..9],4);print @r, "\n" while @r=$p->next;'

There is also a section on permutation, along with additional examples in perlfaq4. It includes several examples and lists some additional modules that handle the details for you. One of Perl's strengths is the size and completeness of the Comprehensive Perl Archive Network (the CPAN).

于 2011-07-25T18:16:41.823 回答