-1

I wanted to write a perl program to generate array with non-repetitive single digit numbers of length 8(Numbers 1-8 in random order) and ninth element to be a underscore. I have written a code like this. I want to use this generated array for a number based puzzle game.

@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";

The program After assigning 7th element, my program started eating memory(10 GB). I don't understand reason for that. I've used a mathematical logic to find the missing number(sum of numbers - 36 (n(n+1)/2)). Why is it eating my memory ? Or is there any effective way of writing the same program ?

Zaid
  • 36,680
  • 16
  • 86
  • 155
aravind ramesh
  • 307
  • 2
  • 15

3 Answers3

6

Is there any effective way of writing the same program?

You bet. A few lines is all you need:

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

In one line:

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

Consider the following:

  • Structure your sub to return a value
  • Avoid writing &randgen; when randgen(); does the same thing
  • Lexical scoping via my
  • use strict; use warnings;
  • Avoid making recursive calls where possible (this is probably the source of your memory consumption)
Community
  • 1
  • 1
Zaid
  • 36,680
  • 16
  • 86
  • 155
  • And maybe avoid C style for loops as well. – flesk Jan 08 '13 at 14:59
  • I will definitely consider your suggestions in my later programs. Thank you – aravind ramesh Jan 08 '13 at 15:06
  • @nickisfat : The C-style loop feature has its uses, like for non-trivial looping (`for ( my $i = 0; $i <=8; $i += 2 ) { ... }`). In the OP's case, it isn't buying anything that a simple `for my $assign ( 0..8 ) { ... }` cannot. – Zaid Jan 08 '13 at 15:21
  • ahh - I though you meant always avoid; was wondering how else to easily achieve more complex looping! – beresfordt Jan 08 '13 at 16:39
5

I haven't looked at what's eating your memory, but here's a simpler approach to achieving what you want:

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

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

print "@mats\n";

Sample output:

3 1 5 8 2 7 4 6 _
flesk
  • 7,439
  • 4
  • 24
  • 33
4

Of course, using shuffle is the One Right Way for your problem. This is a discussion about what went wrong with your code, and how such an algorithm could be expressed more elegantly.

Once you use warnings, you get a warning about deep recursion — the call stack is eating your memory. In your for-loop, you call randgen after the 8th element (index 7) was filled. In that case, the recursion condition is always true (as the eight elems are unique, there now is always an element for which $randigit is equal).

There are a few other things in your code that could be improved:

Loops like for ($i = MIN; $i <= MAX; $i++) are better written as foreach-loops:

for my $i (MIN .. MAX)

Subroutines can take parameters (these are in @_) and return values. This removes the need to use global variables to convey arguments.

Also, use strict; use warnings. This points out many errors, and forces you to declare all your variables (you can declare vars with my).

Here is an updated version of your code. It doesn't feature recursion, but a loop. And it works flawlessly:

#!/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";

Example output:

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.

amon
  • 57,091
  • 2
  • 89
  • 149
  • 1
    Alternative: `do { $rand = int rand( $max ) + 1; } while grep { $_ == $rand } @forbidden;` – Zaid Jan 08 '13 at 15:24
  • @amon This was my second program I've written after learning perl. I Dont have any prior experience. I will definitely incorporate your suggestion in my later programs. Can you please give me some sites where common programming mistakes and tricks to avoid them are listed. – aravind ramesh Jan 09 '13 at 03:57
  • @aravindramesh The perl-begin.org site has a list of [common mistakes](http://perl-begin.org/tutorials/bad-elements/). The page forms a solid coding guideline. The site also offers many other resources. However, getting the base case of recursion wrong is an universal problem. It is said that learning LISP (for example with the old, longish, yet legendary [SICP course](https://www.youtube.com/course?list=ECE18841CABEA24090&feature=edu)) will award you a better understanding of programming. [Higher Order Perl](http://hop.perl.plover.com/) is a bit advanced, but one chapter focusses on recursion – amon Jan 09 '13 at 16:04
  • @aravindramesh [Conway's “Perl Best Practices”](http://books.google.de/books?id=gJf9tI2mytIC&source=gbs_navlinks_s&redir_esc=y) is the definitive Perl coding standard. Sadly, it isn't freely available. Based on the book is the [perlcritic program](https://metacpan.org/module/THALJEF/Perl-Critic-1.118/bin/perlcritic) that can check your code for compliance. – amon Jan 09 '13 at 16:07