I'm working on a model for predicting disease outcomes and had it working until I started trying to create user prompts. The code stops at the "duration" prompt, located at the bottom of this code block. The script just seems to freeze (blinking cursor) and doesn't go anywhere, but there is 100% processor use so it's doing something. I tried printing text and it seems to stop working at the "duration" prompt. The entire code is on GitHub if you feel like trying to run it yourself.
I've looked it over quite a few times and can't see anything wrong anywhere and am hoping some fresh eyes can see something. I'm sure I made a dumb mistake somewhere. Thanks much!
EDIT: If I remove all the code after "my %population," it works.
#!/usr/bin/perl
use 5.10.1;
#use strict;
use warnings;
use Data::Dumper;
use Storable qw(dclone);
print "Enter number of individuals: ";
my $NUM_IND = <STDIN>;
exit 0 if ($NUM_IND eq "");
print "Enter initial number of infections: ";
my $INIT = <STDIN>;
exit 0 if ($INIT eq "");
print "Enter number contacts per individual: ";
my $CONTACT_RATE = <STDIN>;
exit 0 if ($CONTACT_RATE eq "");
print "Enter disease infectious period: ";
my $INFECTIOUS_PERIOD = <STDIN>;
exit 0 if ($INFECTIOUS_PERIOD eq "");
print "Enter disease virulence: ";
my $INFECTIVITY = <STDIN>;
exit 0 if ($INFECTIVITY eq "");
print "Enter disease incubation period: ";
my $INCUB = <STDIN>;
exit 0 if ($INCUB eq "");
print "Enter number of vaccinations per day: ";
my $VAC = <STDIN>;
print "Enter vaccine efficacy: ";
my $EF = <STDIN>;
print "Enter duration of model: ";
my $DURATION = <STDIN>;
exit 0 if ($DURATION eq "");
print "this works";
my %population = ();
Please never comment out use strict. It is probably worse than not including it in the first place. strict is a huge help with finding bugs in your programs and should be embraced, not avoided.
The reason you are not seeing this works is because the output buffer isn't being flushed. If you add
STDOUT->autoflush;
at the top of your program (say, after the use statements) then it will force the buffer to be flushed after every print statement and you will see your text.
The real problem is this line
for (my $i = 0 ; $i = $INIT ; $i++) {
where your condition is the assignment $i = $INIT which will always be true as long as $INIT is non-zero.
I assume you want $i < $INIT, like the other loops.
But it is far better to use Perl's range operator, which is much clearer to read and isn't vulnerable to mistakes like this. Just use
for my $i (0 .. $INIT-1) { ... }
and it will be obvious what is happening.
Related
I am trying to store and print variables dynamically in Perl, by asking user to input number of variables to create, then asking for each of the created variables to add information then output the length of text contained in each of them. In my head I came up with this:
use strict;
use warnings;
sub main {
my %VarStore = ();
print ("How many variables to create: ");
chomp(my $varNum = <STDIN>);
my $counter = 1
while ($counter <= $varNum) {
print "Enter text to variable $counter: \n";
chomp(my $buffer = <STDIN>);
$VarStore{'var'$counter} = $buffer;
$counter ++;
}
while ($counter <= $varNum) {
print "Variable $counter is length($VarStore{'var'$counter}) character long \n";
$counter ++;
}
}
What I would like is:
> How many variables to create: 3
> Enter text to variable 1: ABCQWEPOL
> Enter text to variable 2: xJSAG!HHKSKASK
> Enter text to variable 3: KakA
> Variable 1 is 9 character long
> Variable 2 is 14 character long
> Variable 3 is 4 character long
Any clue why my code is not working? I thought of a hash here so that I can create dynamic variables say with keys var1, var2, var3, etc depending on the input the user gives to create them. Thanks in advance.
You are correct that a hash is a good solution to this problem. You have two problems in your code. First, $VarStore{'var'$counter} is not valid syntax, you need to use the . operator to concatenate strings $VarStore{'var'.$counter}, or you can use double quotes to interpolate variables into strings $VarStore{"var$counter"}.
Unlike variables, you can't directly interpolate function calls into strings, so the length() call should be done separately. Or alternatively you can concatenate strings with the function call. print "Variable $counter is " . length($VarStore{"var$counter"}). " long\n";
Second problem is that after your first while loop completes, the $counter variable you reuse for the next while loop will already be greater than $varNum, so you need to reset it to 1. $counter = 1;
It may be simpler to use foreach loops to iterate through the count. Also, sub main is not needed but if you use it you need to actually call main(); somewhere so it will run.
use strict;
use warnings;
my %VarStore;
print ("How many variables to create: ");
chomp(my $varNum = <STDIN>);
foreach my $counter (1..$varNum) {
print "Enter text to variable $counter: \n";
chomp(my $buffer = <STDIN>);
$VarStore{"var$counter"} = $buffer;
}
foreach my $counter (1..$varNum) {
my $length = length($VarStore{"var$counter"});
print "Variable $counter is $length character long \n";
}
(New to perl)
I have a small perl program that calculates factorials. I'd like to use a while loop so that after the user gets a result, they will be asked "Calculate another factorial? Y/N" and have Y run the code again & have N end the program.
Here's my code:
print"Welcome! Would you like to calculate a factorial? Y/N\n";
$decision = <STDIN>;
while $decision == "Y";
{
print"Enter a positive # more than 0: \n";
$num = <STDIN>;
$fact = 1;
while($num>1)
{
$fact = $fact * $num;
$num $num - 1;
}
print $fact\n;
print"Calculate another factorial? Y/N\n";
$decision = <STDIN>;
}
system("pause");
What's giving me trouble is where to put the while loop and how to make the Y/N option work. I'm also unclear about system("pause") and sleep functions. I do know that system("pause") makes my programs work though.
Your program is almost right, just a few issues:
Please get used to always add use strict; and use warnings; to your scripts. They will
(beyond other things) force you to declare all the variables you use (with my $num=…;)
and warn you about common errors (like typos). Some people consider it a bug that
use strict; and use warnings; aren't turned on by default.
When reading a line from STDIN (or some other filehandle) the read line will contain the
trailing newline character "\n". For your comparison to work you must get rid of that using
the chomp function.
There are two different sets of comparison operators in Perl: one for strings and one for numbers.
Numbers are compared with <, >, <=, >=, ==, and !=. For strings you must use
lt (less-than), gt, le (less-or-equal), ge, eq, and ne. If you use one of the number
operators on strings Perl will try to interpret your string as a number, so $decision == "Y"
would check whether $decision is 0. If you had use warnings; Perl would have noticed you.
Use $decision eq "Y" instead.
The outer while loop had a trailing ; just after the comparison which will give you an endless
loop or a no-op (depending on the content of $decision).
You forgot the = in $num = $num - 1;.
You forgot the quotes " around print "$fact\n";
system("pause") only works on Windows where pause is an external command. On Linux (where
I just tested) there is no such command and system("pause") fails with command not found.
I replaced it with sleep(5); which simply waits 5 seconds.
.
#!/usr/bin/env perl
use strict;
use warnings;
print "Welcome! Would you like to calculate a factorial? Y/N\n";
my $decision = <STDIN>;
chomp($decision); # remove trailing "\n" from $decision
while ( $decision eq 'Y' ) {
print "Enter a positive # more than 0: \n";
my $num = <STDIN>;
chomp($num);
my $fact = 1;
while ( $num > 1 ) {
$fact = $fact * $num;
$num = $num - 1;
}
print "$fact\n";
print "Calculate another factorial? Y/N\n";
$decision = <STDIN>;
chomp($decision);
}
print "ok.\n";
sleep(5); # wait 5 seconds
Always add use warnings and use strict to the beginning of your program.
There are a number of typos in your code that would have been caught by this.
#!/usr/bin/perl
use warnings;
use strict;
print "Welcome! Would you like to calculate a factorial? Enter 'Y' or 'N': ";
my $answer = <STDIN>;
chomp($answer);
while($answer =~ /^[Yy]$/){
my $fact = 1;
print"Enter a positive number greater than 0: ";
my $num = <STDIN>;
chomp($num);
my $number_for_printing = $num;
while($num > 0){
$fact = $fact * $num;
$num--;
}
print "The factorial of $number_for_printing is: $fact\n";
print"Calculate another factorial? Enter 'Y' or 'N': ";
$answer = <STDIN>;
chomp($answer);
}
print "Goodbye!\n";
I'm reading Learning Perl (6th edition) and came upon a code snippet that I couldn't decipher. One of the exercises after chapter 14 asks to build a program that takes as input a string and a substring then finds the indices at which the substring occurs in the string.
Here's the way I did it:
print "Enter a string: ";
chomp($string = <STDIN>);
print "Enter a substring: ";
chomp($sub = <STDIN>);
until ($index == -1) {
print $index, "\n" if defined($index);
$index = index($string, $sub, $index + 1);
}
In the answers section, they show two ways. One is easy to understand and similar to mine, but the other is purposefully obfuscating:
print "Enter a string: ";
chomp($string = <STDIN>);
print "Enter a substring: ";
chomp($sub = <STDIN>);
for (my $pos = –1; –1 !=
($pos = index
+$string,
+$sub,
+$pos
+1
);
push #places, ((((+$pos))))) {
'for ($pos != 1; # ;$pos++) {
print "position $pos\n";#;';#' } pop #places;
}
print "Locations of '$sub' in '$string' were: #places\n";
I have almost no idea what's going on in that for loop. I know it's of the form for (initialize; test; increment) and that it's testing that the index is not -1, which means no more occurrences of the substring. But what's going on with the assignment to $pos? Why are there so many parentheses around +$pos? What's going on after the many parentheses? I'd really appreciate it if someone could walk me through the second part. Please keep in mind I've just started learning Perl a week ago.
BTW, I tried running their code but it gave me this error:
Unrecognized character \xE2; marked by <-- HERE after my $pos = <-- HERE near column 16 at ex14.obfs.pl line 1.
I've simplified your example just a little bit, discarded the useless junk and comments. Hope now it is clear what's going on:
print "Please enter a string: ";
chomp(my $string = <STDIN>);
print "Please enter a substring: ";
chomp(my $sub = <STDIN>);
my #places;
for (my $pos = -1; -1 != ($pos = index $string, $sub, $pos+1); push #places, $pos)
{
#do nothing here
}
print "Locations of '$sub' in '$string' were: #places\n";
The compilation error was due to '–' instead of '-';
The inner loop was in fact a string literal (useless) plus comments (useless), the extra braces are useless as well.
I'm having trouble making yes/no questions with Perl, and I just couldn't figure it out. I'm kinda a noob at this.
#! usr/bin/perl
print "Hello there!\n";
print "What is your favorite game?\n";
$name = <STDIN>;
chomp $name;
print "That's awesome! $name is my favorite game too!\n";
print "Do you think this is a fun script (Y/n) \n";
$status = <STDIN>;
if [ $status = "y" ]: then
print "YAY! I knew you would like it!\n";
if [ $status = "n" ]: then
print "You suck, not me!\n";
What am I doing wrong?
if [ is a shell syntax. In Perl, you should use
if (...) {
Also, = is the assignment operator. For string equality, use eq:
if ($status eq 'y') {
print "YAY\n";
Before comparing, you should chomp $status the same way you're already chomping $name.
Also, note that Y and y are not equal.
Also, your first ("shebang") line misses the starting slash:
#! /usr/bin/perl
if [ $status = "y" ]: then
That's Bourne (or bash) shell syntax. The equivalent Perl code is:
if ($status eq "y") {
# ...
}
eq is equality comparison for strings; == compares numbers.
(The other thing you're doing wrong is not including the error message in your question.)
For example:
$status = <STDIN>;
chomp $status;
if ($status eq "y") {
print "YAY! I knew you would like it!\n";
}
There are some other things you can do to improve your Perl code. For example, you should always have:
use strict;
use warnings;
near the top of the source file (which will require declaring your variables, probably with my). I suggest getting this program working first before worrying about that, but it's definitely something you'll want to do in the long term.
First, always, always put use strict; and use warnings; at the top of your programs. This will catch all sorts of errors such as using = in if statements. = sets a variable's value. == tests numeric equality and eq tests string equality.
Here's your program rewritten. The first line with #! searches your PATH for an executable Perl. This way, you don't have to worry whether Perl is in /usr/bin/perl or /bin/perl or /usr/local/bin/perl.
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say); # Allows the use of the "say" command
say "Hello there!";
print "What is your favorite game? ";
my $name = <STDIN>;
chomp $name;
say "That's awesome! $name is my favorite game too!";
print "Do you think this is a fun script (Y/n) \n";
my $status = <STDIN>;
chomp $status;
if ( $status eq "y" ) {
say "Yay! I knew you would like it!";
}
elsif ( $status eq "n" ) {
say "You suck, not me!";
}
A better way may be to check whether the input started with a y or not:
if ( $status =~ /^y/i ) { # Did it start with a 'y' or 'Y'?
say "Yay! I knew you would like it!";
else {
say "You suck, not me!";
}
Note the use of my to declare variables. This is something use strict; requires and will catch a lot of programming mistakes. Note that say is like print, but I don't have to keep putting \n on the end.
I'm very new to Perl and have been given an assignment that is a simple guessing game where the user is given 8 chances to guess a number between 1 and 100. I keep getting the error mentioned above and cannot figure it out.
Here's my code:
use Modern::Perl;
my ($guess,$target,$counter);
$target = (int rand 100) + 1;
while ($guess < $target)
{
chomp ($guess=<>);
print "Enter guess $counter: ";
$counter++;
if ($guess eq $target) {
print "\nCongratulations! You guessed the secret number $target in $counter";
}
elsif ($guess > $target) {
print "\nYour guess, $guess, is too high.";
}
elsif ($guess < $target) {
print "\nYour guess, $guess, is too low.";
}
else {
print "You lose. The number was $target.";
}
}
Your code suffers a few issues. Here is my code, using a different approach:
#!/usr/bin/perl
use 5.012; # use strict; use feature 'say';
use warnings;
my $number = (int rand 100) + 1;
my $max_guesses = 8;
GUESS: foreach my $guess_no (1..$max_guesses) {
say "($guess_no) Please enter a guess:";
my $guess = <>;
chomp $guess;
unless ($guess =~ /^\d+$/) {
say "Hey, that didn't look like a number!";
redo GUESS;
}
if ($guess == $number) {
say "Congrats, you were on target!";
last GUESS;
} elsif ($guess < $number) {
say "Nay, your guess was TOO SMALL.";
} elsif ($guess > $number) {
say "Nay, your guess was TOO BIG.";
} else {
die "Illegal state";
}
if ($guess_no == $max_guesses) {
say "However, you have wasted all your guesses. YOU LOOSE.";
last GUESS;
}
}
Example usage:
$ perl guess-the-number.pl
(1) Please enter a guess:
15
Nay, your guess was TOO SMALL.
(2) Please enter a guess:
60
Nay, your guess was TOO BIG.
(3) Please enter a guess:
45
Nay, your guess was TOO BIG.
(4) Please enter a guess:
30
Nay, your guess was TOO SMALL.
(5) Please enter a guess:
38
Congrats, you were on target!
(all other corner cases (too many guesses, non-numbers as input) work as expected)
What did I do differently?
I didn't loop while the guess was too small (← bug!). Instead, I did a loop iteration for each guess. However, a while (1) loop would have worked as well.
I did a sanity check on the input, using a simple regular expression. It asserts that the input will be considered numeric by Perl. Else, you get to redo the guess.
I initialize all variables as soon as I declare them. This removes any possibilities of uninitialized values popping up in error messages.
I use proper comparision operators. Perl scalars come in two flavours: stringy and numeric:
Stringy Numeric
lt <
le <=
eq ==
ne !=
ge >=
gt >
cmp <=>
The say function prints the string like print, but appends a newline. This removes akward \ns at the beginning or the end of the string. It makes reading code easier.
You didn't mention which line was line #25! That could be important.
However, look at your while statement. Notice you're comparing $guess with $target, but you haven't set $guess yet. That's set inside you're while loop. This is why you're getting the undefined variable.
I also don't see where $counter is set. That could also be an issue.
Let's look at your while loop a bit closer:
while ( $guess < $target ) {
blah, blah, blah
}
Assuming that there's a $guess, you're looping until the user guesses a number bigger than the $target. Is that what you want? No, you stated that you wanted to give the user eight turns, and that's it. What you want is something more along the lines of:
my $count = 1;
while ( $count <= 8 ) {
$count++;
blah, blah, blah
}
A slightly nicer way of doing this is with a for loop:
for ( my $count = 1; $count <= 8; $count++ ) {
blah, blah, blah
}
However, these C-style for loops are so passé. Plus, there's a clearer way of doing this using the 1..8 syntax. This is the same as saying (1, 2, 3, 4, 5, 6, 7, 8). It's clean and easy to understand:
for my $counter (1..8) {
blah, blah, blah
}
(NOTE: Some people use a foreach keyword instead of a for keyword when doing this type of loop to distinguish it from the C-Style for loop. However, some people (cough! Damian Conway, cough!) frown upon the use of foreach since it doesn't really add clarity.)
Let's look at another aspect of your logic:
if ($guess eq $target) {
print "\nCongratulations! You guessed the secret number $target in $counter";
}
elsif ($guess > $target) {
print "\nYour guess, $guess, is too high.";
}
elsif ($guess < $target) {
print "\nYour guess, $guess, is too low.";
}
else { #Do this if the guess isn't equal to, greater than or less than the target
print "You lose. The number was $target.";
}
When will your else clause be executed? Answer is never. After all, you won't exeucte it unless $guess is neither greater than, equal to, or less than $target. Also, even if you did lose, you're still in that loop.
What you probably want to do is to put the You lose line outside the loop. That way, after the eighth guess and your loop ends, you get the You lose spiel.
Also, let's look at this line:
chomp ( $guess = <> );
print "Enter guess $counter: ";
First, the <> is the null filehandle operator which is very, very different from <STDIN>:
The null filehandle <> is special: it can be used to emulate the behavior of sed and awk, and any other Unix filter program that takes a list of filenames, doing the same to each line of input from all of them. Input from <> comes either from standard input, or from each file listed on the command line. Here's how it works: the first time <> is evaluated, the #ARGV array is checked, and if it is empty, $ARGV[0] is set to "-", which when opened gives you standard input. The #ARGV array is then processed as a list of filenames.
What you want is <STDIN>.
Also notice that you are getting the input before you print out your prompt. You really want to do this instead:
print "Enter guess $counter: ";
chomp ($guess = <STDIN>);
Also, you want to set $| to a nonzero value to turn off buffering. Otherwise, you might still have to enter your $guess before you see the prompt.
Here's the revised program Modern::Perl doesn't work on my system, so I use the three separate pragmas to cover it:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
use constant {
UPPER_RANGE => 100,
MAX_GUESSES => 8,
};
$| = 1;
my $target = int ( ( rand UPPER_RANGE ) + 1 );
for my $counter (1..MAX_GUESSES) {
print "Enter guess #$counter: ";
chomp ( my $guess = <STDIN> );
if ($guess == $target) {
say "Congratulations! You guessed the secret number $target in $counter turns";
exit;
}
elsif ($guess > $target) {
say "Your guess, $guess, is too high.";
}
else {
say "Your guess, $guess, is too low.";
}
}
say "You lose. The number was $target.";
A few things I didn't cover:
When doing numeric comparisons, use the == operator and not eq which is for strings.
I use say instead of print when I can. The say command is like print except it automatically puts a NL on the end for me.
Notice the exit; after I stated you won. I want to end my program at that point.
Notice I don't declare all of my variables until I need them. In my program, $guess and $counter only exists inside the for loop while $target exists both inside and outside the for loop.
I use constant to help avoid mysterious numbers. For example, 1..MAX_GUESSES helps me understand that the loop is going to my maximum guess while 1..8 doesn't explain why I'm going to 8. Also, I can simply change my constant and suddenly you have 10 changes of guessing a number between 1 and 1000.
The problem here is that you aren't explicitly initializing your variables. By default, Perl initializes the variables you create with my() to the undef value. So take a look at the following line in your script:
my ($guess,$target,$counter);
This line creates three variables, all of which are set to undef. For more details on the ins and outs of the my() operator, check out the perlsub docs. Here's a relevant quote from that doc page:
The parameter list to my() may be assigned to if desired, which allows
you to initialize your variables. (If no initializer is given for a
particular variable, it is created with the undefined value.)