1

I am creating a hash that contains names of 4 people. I would like a user to under one of the names and get the profession. At the same time I want the program to continue to ask for entering name if the name input was wrong. However when I run my code, it goes into infinite while loop when name is not found. Any clues how to fix this?

use strict;
use warnings;

sub main {

my %profession = (
    "Emelie"  => "Economist",
    "Hugo" => "Scientist",
    "Maria"  => "Accountant",
    "Linnéa"  => "Medical Doctor",
    );

print ("Enter first name: ");
chomp(my $name = <STDIN>);

my $var = 1;
while ($var) {
    if (exists $profession{$name}) {
        print "The profession is: ", $profession{$name}, "\n";
        $var = 0
    }
    else {
        print "No such name found :-(, try once again\n";
    }

}

}
main();

The infinite loop gives this:

No such name found :-(, try once again

No such name found :-(, try once again

No such name found :-(, try once again

No such name found :-(, try once again

and continues....

Thanks in advance:-)

Community
  • 1
  • 1
tavalendo
  • 857
  • 2
  • 11
  • 30
  • 3
    You never reassign the value of `$name`, which means `$var` can never be anything other than 1, which evaluates to true. – Matt Jacob Oct 13 '18 at 15:23

4 Answers4

3

There is a logic error in your code. For purposes of demonstration I'll switch from using STDIN as your input handle, in its place using DATA so that I can bake-in some test data. I'll also eliminate the outer subroutine, as it's not pertinent to the discussion.

use strict;
use warnings;

my %profession = (
    Emelie    => 'Economist',
    Hugo      => 'Scientist',
    Maria     => 'Accountant',
    Linnéa    => 'Medical Doctor',
);

chomp(my $name = <DATA>);

my $var = 1;
while($var) {
    if (exists $profession{$name}) {
        print "The profession for $name is $profession{$name}\n";
        $var = 0;  # This could just be the 'last' keyword.
    }
    else {
        print "No such name ($name) found. Try again.\n";
    }
}

__DATA__
John
Dave
Maria

As you can see from the sample data you would expect a program with correct logic to terminate after the third try, because Maria is one of your hash keys. But if you run it you see this:

No such name (John) found. Try again.
No such name (John) found. Try again.
No such name (John) found. Try again.
No such name (John) found. Try again.
No such name (John) found. Try again.
No such name (John) found. Try again.
No such name (John) found. Try again.
No such name (John) found. Try again.
^C
Command terminated

(The ^C and Command terminated lines are coming from when I finally hit -c to terminate the run.)

At this point, with the additional information of printing the name that was most recently read from <DATA> it's pretty easy to see that we are only ever looking at John. But why? Because your loop doesn't read from the <DATA> filehandle. You're doing that outside the loop.

Here's a more Perlish way to accomplish what you're wanting to do:

use strict;
use warnings;

my %profession = (
    'Emelie'    => 'Economist',
    'Hugo'      => 'Scientist',
    'Maria'     => 'Accountant',
    'Linnéa'    => 'Medical Doctor',
);

while(my $name = <DATA>) {
    chomp $name;

    if (exists $profession{$name}) {
        print "The profession for $name is $profession{$name}\n";
        last;
    }

    print "No such name ($name) found. Try again.\n";
}

__DATA__
John
Dave
Maria

Now the output will be:

No such name (John) found. Try again.
No such name (Dave) found. Try again.
The profession for Maria is Accountant

The first time through this while loop we read from DATA and obtain John\n. We assign it to $name, chomp it, and then check to see if John exists as a hash key. It doesn't, so we print the name and move on to the next iteration. On the second iteration <DATA> reads Dave\n, chomps it, and checks if it exists. It doesn't, so we print the name and move on to the next iteration.

On the third iteration we <DATA> obtains Maria\n, chomps it, and checks if a hash key Maria exists. It does, so it prints the value associated with that key, and then hits the last statement. last tells the control of flow to exit the enclosing loop immediately. The remainder of the lines within the main block of the loop are skipped, and there are no more iterations. It is often more legible than a sentinel variable such as $var in your example code, as there's no need for the reader of the code to keep track of what state a variable may be in.

So in short, your mistake was only reading from the input filehandle one time, and then expecting your loop to encounter changes in $name despite it only being assigned to one time, before entering the loop. The solution was to move the file read to become part of the loop.

This pattern is documented in perldoc perlvar, which I encourage you to spend a few minutes reading over to gain a better familiarity with the language.

Update: I do see an answer where calling main() again is used as a means of iterating over the file read, and this is not wrong. But in a real script main() is likely to grow larger, and when that happens the file read loop would either need to become an explicit loop again, or be broken out to a different subroutine that can call itself. Additionally, the while loop approach is an idiomatic or common solutions, more likely to be found in code written by others. Using recursion to read a file is a less common pattern.

DavidO
  • 13,812
  • 3
  • 38
  • 66
  • 1
    Thanks a lot. Nice explanation (never thought of using _DATA_ or "last" as I am new to this language). Will thumbs up but will have to stick with @Gilles' recursive function. – tavalendo Oct 13 '18 at 16:03
1

You're resetting $var only if the name is found.

Move this line...

$var = 0

After the if/else.

You also need to move this line...

chomp(my $name = );

Inside the while loop, before the if.

user1751825
  • 4,029
  • 1
  • 28
  • 58
  • But if $var = 0 is after if/else, if the condition falls in "else" then it will ask only once and then stops the while execution if the user enters again the wrong name. The idea is to let while run until a correct match is entered. – tavalendo Oct 13 '18 at 15:40
  • 1
    Will accept @Gilles solution as it works best! Thanks though :-) – tavalendo Oct 13 '18 at 15:45
1

Use this, simplified, no need a loop (recursive function) :

use strict;
use warnings;

sub main {
    my %profession = (
        "Emelie"  => "Economist",
        "Hugo" => "Scientist",
        "Maria"  => "Accountant",
        "Linnéa"  => "Medical Doctor",
    );

    print ("Enter first name: ");
    chomp(my $name = <STDIN>);

    if (exists $profession{$name}) { 
        print "The profession is: ", $profession{$name}, "\n";
    }
    else {
        print "No such name found :-(, try once again\n";
        main();
    }
}
main();
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
0

line 20, a ";" is missing

it is not working because you need to ask the name again when it is not found otherwise it will loop ($var won't change to 0)

simple example :

else{
    print "No such name found :-(, try once again\n";
    chomp($name = <STDIN>);
}
cedsam
  • 1
  • 2