-1

我想编写一个 perl 程序来生成长度为 8 的非重复单个数字(数字 1-8 以随机顺序排列)和第九个元素为下划线的数组。我写过这样的代码。我想将此生成的数组用于基于数字的益智游戏。

@mat = (0,0,0,0,0,0,0,0,0);

sub randgen { 

    $randigit = int(rand(9));

    if ($randigit == 0) {  
        &randgen;   
    }

    elsif (    $mat[0] == $randigit
            || $mat[1] == $randigit
            || $mat[2] == $randigit
            || $mat[3] == $randigit
            || $mat[4] == $randigit
            || $mat[5] == $randigit
            || $mat[6] == $randigit
            || $mat[7] == $randigit
            || $mat[8] == $randigit
          )
    {
        &randgen;
    }
}

&randgen;

for ( $assign = 0; $assign <= 8; $assign++) {

    $mat[$assign] = $randigit;
    print "@mat \n"; # To see to what extent the program has generated the array
    &randgen;
}

for ($i = 0; $i <= $#mat; $i++) {   

    $sum = $sum + $mat[$i];
}

$miss = 36 - $sum ;
$mat[7] = $miss;
$mat[8] = "_";
print "@mat \n";

程序分配第 7 个元素后,我的程序开始消耗内存(10 GB)。我不明白这是为什么。我使用数学逻辑来找到丢失的数字(数字总和 - 36 (n(n+1)/2))。为什么要吃掉我的记忆?还是有任何有效的方法来编写相同的程序?

4

3 回答 3

6

有没有编写相同程序的有效方法?

你打赌。您只需要几行代码:

use List::Util 'shuffle';       # import shuffle
my @array = shuffle( 1 .. 8 );  # shuffle 1 to 8
push @array, '_';               # add the underscore

在一行中:

my @array = ( shuffle( 1 .. 8 ), '_' );

考虑以下:

  • 将您的 sub 结构化为return一个值
  • 避免写&randgen;什么时候randgen();做同样的事情
  • 词法作用域通过my
  • use strict; use warnings;
  • 尽可能避免进行递归调用(这可能是您的内存消耗的来源)
于 2013-01-08T14:43:02.620 回答
5

我还没有查看是什么在吞噬你的记忆,但这里有一个更简单的方法来实现你想要的:

#!/usr/bin/env perl
use strict;
use warnings;
use List::Util 'shuffle';

my @mats = shuffle 1 .. 8;
push @mats, '_';

print "@mats\n";

样本输出:

3 1 5 8 2 7 4 6 _
于 2013-01-08T14:42:59.970 回答
4

当然,使用shuffle是解决您问题的一种正确方法。这是关于您的代码出了什么问题的讨论,以及如何更优雅地表达这种算法。

一旦你use warnings,你就会收到关于深度递归的警告——调用堆栈正在吃掉你的内存。在您的 for 循环中,您randgen在第 8 个元素(索引 7)被填充后调用。在这种情况下,递归条件总是为真(因为八个元素是唯一的,所以现在总是有一个元素$randigit相等)。

您的代码中还有其他一些可以改进的地方:

像这样for ($i = MIN; $i <= MAX; $i++)的循环最好写成 foreach-loops:

for my $i (MIN .. MAX)

子例程可以接受参数(这些在 中@_)并返回值。这消除了使用全局变量来传递参数的需要。

另外,use strict; use warnings. 这指出了许多错误,并迫使您声明所有变量(您可以使用 vars 声明my)。

这是您的代码的更新版本。它没有递归,而是一个循环。它完美地工作:

#!/usr/bin/perl

use strict; use warnings;

# randgen takes
#   - the largest number that may be used, and
#   - a list of all forbidden numbers.
sub randgen { 
    my ($max, @forbidden) = @_; # unpack arguments
    my $rand;
    do {
        $rand = int rand($max + 1);
    } while grep {$_ == $rand} 0, @forbidden;
    return $rand;  # return the rand value.
}

my $digits = 8;
my @mat;  # don't prefill fields.
for ( 1 .. $digits ) {                 # do $digits times 
    push @mat, randgen($digits, @mat); # push appends item to array
    print "@mat\n";
}
push @mat, "_";

print "@mat\n";

示例输出:

8
8 4
8 4 7
8 4 7 2
8 4 7 2 3
8 4 7 2 3 5
8 4 7 2 3 5 1
8 4 7 2 3 5 1 6
8 4 7 2 3 5 1 6 _

The grep builtin takes an expression or a block, plus a list of values. It returns all values to which the expression evaluates to true. The current item is accessible via $_. In my loop, it returns true as long as there is a value in @forbidden or the value 0 that is equal to $rand.

于 2013-01-08T15:21:21.760 回答