Trying to write a simple loop - perl

Could someone help me with a loop please. I'm meant to be writing a program which simply asks you to guess a number between 1 and 10. If it's not the correct answer you get another chance, etc.
I can get my script to print correct/not correct one time, but how do I add into this script a possibility for the user to try again (until they guess the right number)?
Here's my basic script, which I'm sure is very simplistic and probably full of errors. Could someone help me sort out this simple problem?
Sorry for the bad layout, but I don't understand how to place my script on this site, sorry!
use strict;
use warnings;
print "Hello, I've thought of a number, do you know what number it is?\n";
sleep (1);
print "Try and guess, type in a number between 1 and 10!\n";
my $div = <STDIN>;
my $i = 0;
my $int = int(rand (10)) + 1;
chomp $div;
if ($div < $int) {
print ("The number I though of is higher than $div, try again?\n");
}
if ($div > $int) {
print ("The number I though of is lower that $div, try again?\n");
}
if ($div == $int) {
print ("Amazing, you've guessed mt number\n");
}

The more straightforward approach would be a while loop.
use strict;
use warnings;
print "Hello, I've thought of a number, do you know what number it is?\n";
sleep (1);
my $int = int(rand (10)) + 1;
print "Try and guess, type in a number between 1 and 10!\n";
while (my $div = <STDIN>) {
chomp $div;
if ($div < $int) {
print "The number I though of is higher than $div, try again?\n";
}
elsif ($div > $int) {
print "The number I though of is lower that $div, try again?\n";
}
else {
print "Amazing, you've guessed mt number\n";
last;
}
}
While (pun intended) your code already is very good (you are using strict and warnings and there are no syntax errors, yay for that!) there are some things I changed, and some more where I would suggest improvement.
But first, let's look at the loop. The program will stay in the while loop as long as the condition is true. Since everything the user can input (even an empty line) is considered true by Perl, this is forever. Which is fine, as there is a condition to exit the loop. It's in the else part of the if. The last statement tells Perl to exit the loop. If the else is not executed, it will go back to the start of the while block and the user has to try again. Forever.
The changes I made:
- You don't need $i as you did not use it
- You used three seperate if statements. Since only one of the three conditions can be true in this case, I merged them into one
- No need for the parens () with print
Suggestions:
- You should name your variables for what they do, not what they are. $int is not a good name. I'd go with $random, or even $random_number. Verbosity is important if you have to come back to your code at a later point.
- There is a function called say that you can enable with use feature 'say';. It adds say "stuff" as an equivalent to print "stuff\n".
Edit:
If you want to add other conditions that do not directly relate to which number the user has entered, you can add another if.
while (my $div = <STDIN>) {
chomp $div;
if ($div eq 'quit') {
print "You're a sissy... the number was $int. Goodbye.\n";
last;
}
if ($div < $int) {
print "The number I though of is higher than $div, try again?\n";
}
elsif ($div > $int) {
print "The number I though of is lower that $div, try again?\n";
}
else {
print "Amazing, you've guessed mt number\n";
last;
}
}
You can also add a check to make sure the user has entered a number. Your current code will produce warnings if a word or letter was is entered. To do that, you will need a regular expression. Read up on them in perlre. The m// is the match operator that works together with =~. The \D matches any character that is not a number (0 to 9). next steps over the rest of the while block and begins with the check of the while condition.
while (my $div = <STDIN>) {
chomp $div;
if ($div =~ m/\D/) {
print "You may only guess numbers. Please try again.\n";
next;
}
# ...
}
Thus, the complete check means 'look at the stuff the user has entered, and if there is anything else than a number in it at all, complain and let him try again'.

use an until loop
my $guessed = 0;
do {
print "Try and guess, type in a number between 1 and 10!\n";
my $div = <STDIN>;
...;
if ($div == $int) {
print ("Amazing, you've guessed mt number\n");
$guessed = 1;
}
} until ($guessed)

Related

how to check an empty input and regulate the type of input in perl

Guys how do i check an empty input in perl? Including spaces,tabs,newlines etc..
here's my sample code but its not working: what am i doing wrong?
my $number=int (1+rand 100);
while (<>) {
chomp $_;
last if ($_=~/exit|quit/i or $_ eq $number);
print "too high! \n" if (defined $_ && $_ > $number);
print "too low!\n" if (defined $_ && $_ < $number);
print $_;
}
So basically, the user input something, if it's a number it compares to the default random number. It prints low or high depending on the number. But when i just press enter without entering something it still goes to that if statement and gives an error that what i entered isnt numeric (due to this code $_ < $number).
So another question is how to handle input to allow only the word "exit" or "quit" and numbers. Other than that it exits.
The while(<>){...} will loop as long as the return value of the <> is defined, i.e. you aren't at EOF. So $_ is always defined inside the loop.
To assert that some input is numeric, you can use looks_like_number from Scalar::Util or use a simple regex:
unless (/\A[0-9]+\z/) {
print "not numeric!\n";
next;
}
After that, we can treat the value of $_ as a number integer, and can be sure that use warnings won't complain. E.g.
# remove newline from input
chomp;
# Test for abort condition
last if /\b(?:quit|exit)\b/;
# Assert numeric input
unless (/\A[0-9]+\z/) {
print "not numeric!\n";
next;
}
# Check input against secret $number
if ($_ == $number) {
print "Correct!\n";
last;
} elsif ($_ < $number) {
print "too low\n";
} elsif ($_ > $number) {
print "too high\n";
}
Sorry for the silly question, Got the answer to my first question.
you just have to add this code to the first condition
$_=~/^\s*$/
which compares if the input is any whitespace
and for the second one to limit only the "exit" and "quit" as a valid non digit input add this one to the first regex comparison in the first condition
$_=~/(exit|quit|\D)/i
notice the \D which only matches non digit characters. But since its an "OR" it will short circuit once a specific non digit character (exit or quit) is entered, terminating the loop instantly.
Thanks guys

Perl need the right grep operator to match value of variable

I want to see if I have repeated items in my array, there are over 16.000 so will automate it
There may be other ways but I started with this and, well, would like to finish it unless there is a straightforward command. What I am doing is shifting and pushing from one array into another and this way, check the destination array to see if it is "in array" (like there is such a command in PHP).
So, I got this sub routine and it works with literals, but it doesn't with variables. It is because of the 'eq' or whatever I should need. The 'sourcefile' will contain one or more of the words of the destination array.
// Here I just fetch my file
$listamails = <STDIN>;
# Remove the newlines filename
chomp $listamails;
# open the file, or exit
unless ( open(MAILS, $listamails) ) {
print "Cannot open file \"$listamails\"\n\n";
exit;
}
# Read the list of mails from the file, and store it
# into the array variable #sourcefile
#sourcefile = <MAILS>;
# Close the handle - we've read all the data into #sourcefile now.
close MAILS;
my #destination = ('hi', 'bye');
sub in_array
{
my ($destination,$search_for) = #_;
return grep {$search_for eq $_} #$destination;
}
for($i = 0; $i <=100; $i ++)
{
$elemento = shift #sourcefile;
if(in_array(\#destination, $elemento))
{
print "it is";
}
else
{
print "it aint there";
}
}
Well, if instead of including the $elemento in there I put a 'hi' it does work and also I have printed the value of $elemento which is also 'hi', but when I put the variable, it does not work, and that is because of the 'eq', but I don't know what else to put. If I put == it complains that 'hi' is not a numeric value.
When you want distinct values think hash.
my %seen;
#seen{ #array } = ();
if (keys %seen == #array) {
print "\#array has no duplicate values\n";
}
It's not clear what you want. If your first sentence is the only one that matters ("I want to see if I have repeated items in my array"), then you could use:
my %seen;
if (grep ++$seen{$_} >= 2, #array) {
say "Has duplicates";
}
You said you have a large array, so it might be faster to stop as soon as you find a duplicate.
my %seen;
for (#array) {
if (++$seen{$_} == 2) {
say "Has duplicates";
last;
}
}
By the way, when looking for duplicates in a large number of items, it's much faster to use a strategy based on sorting. After sorting the items, all duplicates will be right next to each other, so to tell if something is a duplicate, all you have to do is compare it with the previous one:
#sorted = sort #sourcefile;
for (my $i = 1; $i < #sorted; ++$i) { # Start at 1 because we'll check the previous one
print "$sorted[$i] is a duplicate!\n" if $sorted[$i] eq $sorted[$i - 1];
}
This will print multiple dupe messages if there are multiple dupes, but you can clean it up.
As eugene y said, hashes are definitely the way to go here. Here's a direct translation of the code you posted to a hash-based method (with a little more Perlishness added along the way):
my #destination = ('hi', 'bye');
my %in_array = map { $_ => 1 } #destination;
for my $i (0 .. 100) {
$elemento = shift #sourcefile;
if(exists $in_array{$elemento})
{
print "it is";
}
else
{
print "it aint there";
}
}
Also, if you mean to check all elements of #sourcefile (as opposed to testing the first 101 elements) against #destination, you should replace the for line with
while (#sourcefile) {
Also also, don't forget to chomp any values read from a file! Lines read from a file have a linebreak at the end of them (the \r\n or \n mentioned in comments on the initial question), which will cause both eq and hash lookups to report that otherwise-matching values are different. This is, most likely, the reason why your code is failing to work correctly in the first place and changing to use sort or hashes won't fix that. First chomp your input to make it work, then use sort or hashes to make it efficient.

How to get rid of this infinite loop in my code? (Making a number guessing game program)

I have to create a number guessing game program and have written the code but after inputting my first number guess, the output turns into an infinite loop and keeps repeating forever, so I am forced to shut down my program. It seems to be an error with my "{}" but I can't figure out where the error is. I have to let the user guess 8 different times, and am stuck on the 1st guess result because it keeps repeating. Here is my code:
print "Welcome to the Perl Whole Number Guessing Game!\n Please enter a number between 1 and 100 and I will tell you if the number you're trying to guess is higher or lower than your guess. You have up to 8 chances to guess the number.\n";
my ($guess, $target, $counter); #my variables
$target = (int rand 100) +1; #must be between 1-100
$counter =1;
#1st guess:
print "Enter guess #$counter:";
chomp ($guess = <>);
while ($guess != $target)
{ if ($guess < $target)
{
print "Your guess, $guess, is too low. Try again.\n ";
}
else
{ print "Your guess, $guess, is too high. Try again.\n ";
}}
until ($guess ==$target)
{
print "Congratulations! You guessed the secret number ($target) in $counter tries!\n";
}
$counter ++;
...Then that exact code repeats 8 times until the last bit says this:
#8th and final guess:
print "Enter guess #$counter:";
chomp ($guess = <>);
while ($guess != $target)
{ if ($guess < $target)
{print "I'm sorry, you didn't guess the secret number, which was $target.\n";
}
else
{print "I'm sorry, you didn't guess the secret number, which was $target.\n";
}}
until ($guess ==$target)
{ print "Congratulations! You guessed the secret number ($target) in $counter tries!\n";
}
I just want to be able to have the code ask me to guess again all 8 times, without having the very first guess go on an infinite loop.
NOTE: I am in a beginning beginning Perl programming class and can't use any fancy, difficult code, basically the simplest solution is best and really the only thing I can kinda understand.
Thanks for any help!
Repeating code is something you should basically never do, except perhaps in very simple cases. Since you have a specific count, just use a for loop with a counter.
You should also always use use warnings; use strict;, because it will help you avoid simple mistakes and give you informative errors.
Also, your if statement will not detect correct guesses. You will need to use elsif (yes, no "e") to also check if the number is too high.
my $guesses = 8;
for my $counter (1 .. $guesses) {
print "Enter guess #$counter:";
chomp (my $guess = <>);
if ($guess < $target) {
print "Your guess, $guess, is too low. Try again.\n ";
} elsif ($guess > $target) {
print "Your guess, $guess, is too high. Try again.\n ";
} else {
print "Congratulations! You guessed the secret number ($target) in $counter tries!\n";
}
}
You need to put your increment inside your while, first of all.
Repeating code is a terrible way of making a program. That's what loops are for.
You just need to break out of the loop after the 8th try.
PERL is not my lang of choice, but in most languages, syntax like this will work:
while (($guess != $target) && ($count <= 8))
{...}
while ($guess != $target)
means WHILE the user's guess is NOT the random number the computer has "thought of"...
DO what is enclosed in the brackets...
WHICH IS : to print a message (whether the guess was "low", "high" or correct)
Now, think about it... Let's say WE play this game... and I'm the one which has to guess YOUR number... Let's simulate it...
You think of a number (let's say : target = 34)
I'm making a guess (let's say : 45)
WHILE my guess is not correct, tell me "it's too high"
So, you get my point : the rest of the code will NEVER be executed. As you will simply keep telling me that I'm wrong, without letting make another guess...
So, why don't you do... something with that evil... WHILE?
I don't know perl, but logically you could put the whole thing in one loop
While (guess != target && wrongAnswer < 8)
{
}
If the answer is not equal to the target increase the wrongAnswer counter.
As the other posters have pointed out, you need to increment somewhere inside your while loop. Also, keep in mind that you don't need the following code block:
until ($guess ==$target) {
print "Congratulations! You guessed the secret number ($target) in $counter tries!\n";
}
If you reach this point in your code, the condition that $guess is equal to $target will already have been satisfied. So, checking that again is redundant. You can skip the until block and go right to the print statement after the while loop.
The most important remaining issue is that you're only asking for input once, before the while block begins. So, during the while block the user never gets the chance to guess a second time. The guess never changes and the loop will go on forever. To avoid code repetition, you could put the guess code into a subroutine:
sub guess {
print "Enter guess #$counter:";
chomp( $guess = <> );
}
Then call this subroutine once before you get to the while loop:
guess();
Then once just before the end of your while block:
guess();
That will reseed the $guess variable before the next pass at the while loop, which will break you out of your infinite loop. I would have given you an entire code sample, but as this is tagged as "homework", that would rob you of the benefit of working over these issues on your own. :)

Perl Global variable uninitialized

I'm new to perl so please bear with me.
I have script that is parsing a CSV file. To make things easier to debug I am using a state machine FSA::Rules (works great love it).
Every thing is going well only now I need to make my logs make sense, as part of this I need to record line numbers so my program looks some thing like this.
my $line = '';
my $lineCount = 0;
sub do {
...
#CSV opened
...
#State machine stuff happens here
readLine;
if ($line =~ m/.*Pattern*/){
#do stuff
}
}
sub readLine{
$line = <CSV>;
$lineCount ++;
}
But I get the following error
Use of uninitialized value $line in pattern match (m//) at
Any one know why $line would not be initialized?
Thanks.
When you reach end of file, $line = <CSV> will assign the undefined value to $line. The usual idiom is to check whether the readline function (which is implicitly called by the <> operator) returned a good value or not before proceeding ...
while (my $line = <CSV>) {
# guaranteed that $line has a defined value
...
}
but you with your sequence of calls, you are avoiding that check. Your current code also increments $lineCount even when <CSV> does not return a good value, which may not be what you want either.

Perl comparison operation between a variable and an element of an array

I am having quite a bit of trouble with a Perl script I am writing. I want to compare an element of an array to a variable I have to see if they are true. For some reason I cannot seem to get the comparison operation to work correctly. It will either evaluate at true all the time (even when outputting both strings clearly shows they are not the same), or it will always be false and never evaluate (even if they are the same). I have found an example of just this kind of comparison operation on another website, but when I use it it doesn't work. Am I missing something? Is the variable type I take from the file not a string? (Can't be an integer as far as I can tell as it is an IP address).
$ipaddress = '192.43.2.130'
if ($address[0] == ' ')
{
open (FH, "serverips.txt") or die "Crossroads could not find a list of backend servers";
#address = <FH>;
close(FH);
print $address[0];
print $address[1];
}
for ($i = 0; $i < #address; $i++)
{
print "hello";
if ($address[$i] eq $ipaddress)
{print $address[$i];
$file = "server_$i";
print "I got here first";
goto SENDING;}
}
SENDING:
print " I am here";
I am pretty weak in Perl, so forgive me for any rookie mistakes/assumptions I may have made in my very meager bit of code. Thank you for you time.
if ($address[0] == ' ')
{
open (FH, "serverips.txt") or die "Crossroads could not find a list of backend servers";
#address = <FH>;
close(FH);
You have several issues with this code here. First you should use strict because it would tell you that #address is being used before it's defined and you're also using numeric comparison on a string.
Secondly you aren't creating an array of the address in the file. You need to loop through the lines of the file to add each address:
my #address = ();
while( my $addr = <FH> ) {
chomp($addr); # removes the newline character
push(#address, $addr);
}
However you really don't need to push into an array at all. Just loop through the file and find the IP. Also don't use goto. That's what last is for.
while( my $addr = <FH> ) {
chomp($addr);
if( $addr eq $ipaddress ) {
$file = "server_$i";
print $addr,"\n";
print "I got here first"; # not sure what this means
last; # breaks out of the loop
}
}
When you're reading in from a file like that, you should use chomp() when doing a comparison with that line. When you do:
print $address[0];
print $address[1];
The output is on two separate lines, even though you haven't explicitly printed a newline. That's because $address[$i] contains a newline at the end. chomp removes this.
if ($address[$i] eq $ipaddress)
could read
my $currentIP = $address[$i];
chomp($currentIP);
if ($currentIP eq $ipaddress)
Once you're familiar with chomp, you could even use:
chomp(my $currentIP = $address[$i]);
if ($currentIP eq $ipaddress)
Also, please replace the goto with a last statement. That's perl's equivalent of C's break.
Also, from your comment on Jack's answer:
Here's some code you can use for finding how long it's been since a file was modified:
my $secondsSinceUpdate = time() - stat('filename.txt')->mtime;
You probably are having an issue with newlines. Try using chomp($address[$i]).
First of all, please don't use goto. Every time you use goto, the baby Jesus cries while killing a kitten.
Secondly, your code is a bit confusing in that you seem to be populating #address after starting the if($address[0] == '') statement (not to mention that that if should be if($address[0] eq '')).
If you're trying to compare each element of #address with $ipaddress for equality, you can do something like the following
Note: This code assumes that you've populated #address.
my $num_matches=0;
foreach(#address)
{
$num_matches++ if $_ eq $ipaddress;
}
if($num_matches)
{
#You've got a match! Do something.
}
else
{
#You don't have any matches. This may or may not be bad. Do something else.
}
Alternatively, you can use the grep operator to get any and all matches from #address:
my #matches=grep{$_ eq $ipaddress}#address;
if(#matches)
{
#You've got matches.
}
else
{
#Sorry, no matches.
}
Finally, if you're using a version of Perl that is 5.10 or higher, you can use the smart match operator (ie ~~):
if($ipaddress~~#address)
{
#You've got a match!
}
else
{
#Nope, no matches.
}
When you read from a file like that you include the end-of-line character (generally \n) in each element. Use chomp #address; to get rid of it.
Also, use last; to exit the loop; goto is practically never needed.
Here's a rather idiomatic rewrite of your code. I'm excluding some of your logic that you might need, but isn't clear why:
$ipaddress = '192.43.2.130'
open (FH, "serverips.txt") or die "Crossroads could not find a list of backend servers";
while (<FH>) { # loop over the file, using the default input space
chomp; # remove end-of-line
last if ($_ eq $ipaddress); # a RE could easily be used here also, but keep the exact match
}
close(FH);
$file = "server_$."; # $. is the line number - it's not necessary to keep track yourself
print "The file is $file\n";
Some people dislike using perl's implicit variables (like $_ and $.) but they're not that hard to keep track of. perldoc perlvar lists all these variables and explains their usage.
Regarding the exact match vs. "RE" (regular expression, or regexp - see perldoc perlre for lots of gory details) -- the syntax for testing a RE against the default input space ($_) is very simple. Instead of
last if ($_ eq $ipaddress);
you could use
last if (/$ipaddress/);
Although treating an ip address as a regular expression (where . has a special meaning) is probably not a good idea.