Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I am attempting to randomly infect a certain number of people in a population notated by @pop (not shown). The variable $pop stores the index size of the array that contains my population. As of now this piece of code works but is still failing in one small area. Everytime it encounters a second draw that is equal to the first draw, it produces a number and prints it although it doesn't belong to either the first or second draw.

sub randompick_infect { my $range = $pop; $rand = int(rand($range)); print "$rand\n"; $pop[$rand]{status} = 1; } print "How many random infections? \n"; my $answerinfect = <>; for ($i=1; $i<$answerinfect; $i++){ randompick_infect; $firstcontact = $rand; print "first contact $firstcontact\n"; do{ randompick_infect; $secondcontact = $rand; print "second contact $secondcontact\n"; if ($secondcontact == $firstcontact){ randompick_infect; } else { $j=-1; } }while($j!=-1); } printarray; print "You have infected $answerinfect people.\n";

Any help appreciated.

Replies are listed 'Best First'.
Re: infinite loop does not stop
by frozenwithjoy (Priest) on Jul 14, 2012 at 20:23 UTC
    The reason you enter an infinite loop is because you never get to: else { $j != -1; }

    The reason for this is that $rand (and, therefore, $firstcontact and $secondcontact) are always 0 because nothing ever gets passed to your subroutine which means you are effectively writing: $rand = int( rand() );

    To start, I suggest adding:

    use strict; use warnings;

    Also, take a look at perlsub to better understand how to pass values to subroutines and return what you want. While you are there, be sure to pay attention to the parts about scope since you typically want your variables in your subroutine to be isolated.

Re: infinite loop does not stop
by Athanasius (Cardinal) on Jul 15, 2012 at 08:36 UTC

    From the final print statement in the original code, it seems the intention is to mark a random sample of the population as infected, the size of this sample being input by the user as $answerinfect. If this interpretation is correct, the OP’s code does not work correctly, even setting aside the infinite loop, because each iteration of the outer for loop infects 2 distinct individuals, so the outcome is anywhere from $answerinfect to (2 * $answerinfect) infections (depending on the amount of overlap between loop iterations).

    The following code incorporates the suggestions of aitap and frozenwithjoy, and insures that exactly $answerinfect individuals are infected. I have assumed (1) that the status field records infection, 0 for uninfected and 1 for infected; and (2) that the initial population contains no infected individuals.

    #! perl use strict; use warnings; use Data::Dumper; use constant { WELL => 0, SICK => 1, }; my @pop = ( { name => 'Fred', status => WELL, }, { name => 'Wilma', status => WELL, }, { name => 'Barney', status => WELL, }, { name => 'Betty', status => WELL, }, ); (my $pop = scalar @pop) > 0 or die "Empty populatio +n\n"; print "How many random infections? (Enter a number between 1 and $pop) +:\n"; chomp(my $answerinfect = <>); ($answerinfect > 0) && ($answerinfect <= $pop) or die "Invalid selecti +on\n"; for (1 .. $answerinfect) { for (my $new_infection = 0; !$new_infection;) { my $contact = int(rand($pop)); if ($pop[$contact]{status} == WELL) { $pop[$contact]{status} = SICK; $new_infection = 1; } } } print Dumper(\@pop); printf "You have infected %d %s: ", $answerinfect, ($answerinfect == 1) ? 'person' : ' +people'; print join(', ', sort map { $_->{name} } grep { $_->{status} == SICK } + @pop);

    Typical output:

    How many random infections? (Enter a number between 1 and 4): 2 $VAR1 = [ { 'status' => 0, 'name' => 'Fred' }, { 'status' => 0, 'name' => 'Wilma' }, { 'status' => 1, 'name' => 'Barney' }, { 'status' => 1, 'name' => 'Betty' } ]; You have infected 2 people: Barney, Betty

    Some notes:

    • The introduction of some elementary error checking helps prevent the generation of an infinite loop.
    • Named constants improve readability and code clarity.
    • The code to update a person from WELL to SICK has been moved from within the randompick_infect subroutine into the inner loop in the main code. As a consequence, randompick_infect’s remaining code is now short enough to be inlined. In any case, with the revised logic this subroutine would now be called only once anyway.
    • The do {} while construct has been replaced with a for loop. Given Perl’s rules for lexical scoping (i.e., my variables), a for or while loop is often a simpler alternative to the do {} while construct. (And see perlfunc on the limitations of do loops in Perl.)

    HTH,

    Athanasius <°(((><contra mundum

Re: infinite loop does not stop
by aitap (Curate) on Jul 14, 2012 at 20:12 UTC

    You may want to chomp (my $answerinfect = <>); because otherwise it would contain "entered_answer\n". Does this give you any warnings?

    What scope does the $j variable belong to? Did you try last instead of setting variable?

    Sorry if my advice was wrong.
Re: infinite loop does not stop
by Anonymous Monk on Jul 15, 2012 at 14:11 UTC
    Infinite loop does not stop ...


    Yes, Little Eagle.   Now, back to work.   Wax on .. Wax off ..
Re: infinite loop does not stop
by linuxkid (Sexton) on Jul 15, 2012 at 15:07 UTC

    infinite loop does not stop Wow. try re-reading that, it kinda is a little misleading. They are called infinite loops for a reason, the ain't supposed to stop!

    --linuxkid


    imrunningoutofideas.co.cc