2013-01-08 53 views
-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))。爲什麼它吃我的記憶?或者是否有任何有效的方法來編寫相同的程序?

回答

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), '_'); 

考慮以下幾點:

  • 結構的子到return
  • 避免寫&randgen;randgen();做同樣的事情
  • Lex通過my
  • use strict; use warnings;
  • 避免在可能的情況進行遞歸調用的iCal作用域(這可能是你的內存消耗源)
+0

也許避免C風格的循環。 – flesk

+0

我一定會在以後的課程中考慮你的建議。謝謝 –

+0

@flesk:肯定 – Zaid

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 _ 
4

當然,使用shuffle是一個正確答案您的問題。這是關於你的代碼出了什麼問題的討論,以及如何更優雅地表達這樣的算法。

一旦你use warnings,你會得到一個關於深遞歸的警告 - 調用棧正在吃掉你的記憶。在你的for循環中,在第8個元素(索引7)填滿之後,請致電randgen。在這種情況下,遞歸條件總是如此(因爲八個元素是唯一的,現在總是有一個元素,其中$randigit是相等的)。

有代碼中的一些其他的東西可以改進:

循環像for ($i = MIN; $i <= MAX; $i++)更好寫成的foreach循環:

for my $i (MIN .. MAX) 

子程序可以帶參數(這些都是@_)並返回值。這消除了使用全局變量來傳遞參數的需要。

另外,use strict; use warnings。這指出了很多錯誤,並迫使你聲明所有的變量(你可以用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 _ 

grep內置接受一個表達式或塊,加上值的列表。它返回表達式評估爲true的所有值。當前項目可通過$_訪問。在我的循環中,只要@forbidden中的值或值0等於$rand就會返回true。

+1

可選:'do {$ rand = int rand($ max)+ 1; }而grep {$ _ == $ rand} @forbidden;' – Zaid

+0

@amon這是我學習Perl之後編寫的第二個程序。我沒有任何經驗。我一定會將你的建議納入我後來的計劃中。你能否給我一些網站,其中列出了常見的編程錯誤和技巧,以避免它們被列出。 –

+0

@aravindramesh perl-begin.org網站有一個[常見錯誤]列表(http://perl-begin.org/tutorials/bad-elements/)。該頁面形成了一個堅實的編碼準則。該網站還提供了許多其他資源。但是,獲得遞歸的基本案例是一個普遍問題。據說學習LISP(例如,用舊的,長期的,但傳奇的[SICP課程](https://www.youtube.com/course?list=ECE18841CABEA24090&feature=edu))將會給你更好的編程理解。 [高階Perl](http://hop.perl.plover.com/)有點高級,但有一章着重於遞歸 – amon