http://www.perlmonks.org?node_id=706337

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

Perl Monks I'm new to you, Perl and programming in general. As I build my skills, I am fully aware that my questions, as well as my knowledge of Perl, are at best basic, at least trivial, so please forgive on this point, on all I others I will be very careful to comply with posting regulations, which I have read thoroughly.

I am writing a basic trivia game, and for some reason, this if statement will not work:

if ($guess eq $correct[$i]) { # if guess is correct, reply print "You Rock!\n"; $i++; } else { print "You Suck!\n"; # if incorrect, reply and save answer to + be calculated @incorrect = (@incorrect, $guess);
I have looked up several examples in my lama book, all of them seemingly in agreement with mine, as far as formatting and syntax go. This is inside of a loop, in more code, but I have spent several days breaking it into pieces, and have pinpointed this piece as the problem.

Regardless of the answer given, it skips the first "if" and goes right to the "else", i.e., if I give the correct answer, it recognizes it as incorrect, saves it accordingly, and prints the "else" reply every time. I have checked and double-checked, in several test routines, that all of my variables hold the correct values, so I know the problem is not there. However, just in case I missed something there:

my @questions = ("Name the definitive rock band.." , "What is their be +st song?" , "What is their best album?"); my @correct = ("The Rolling Stones" , "Moonlight Mile" , "Sticky Finge +rs"); my $grade = 100 - length(my @incorrect) * 33.3; my @incorrect; my $i = 0; my $guess;

After reading one reference, I saw some similar code in example with this added between the "if and "else":

} elsif ($i < 2) { $i = $i + 1;
However this only compounded the problem. I do believe that this piece is an insight to what I'm doing wrong, or missing, but I can't seem to wrap my head around it. I have looked up control structures, syntax, etc. of this piece in several places, lama book (first edition), perlop links from here, their FAQ and the camel book, which I really am not even equipped to navigate just yet.

Please note, that the simplicity, i.e. lack of regular expressions, not using a hash for the questions and answers and the like, I fully realize, I began this in my first month and have now been asked to de-bug the original code, with out upgrading it to what I've learned since, in order to completely master my use of control structures, before I move on to my next project. Also, I certainly am not asking for ya'll to do my work for me, I am very aware of the problems with that, for you and my skills, but I have been trying to figure my mistake out for quite some time now.

It may be something easily referenced, if it is, I didn't know where to look.

Basically, I really would very much like a hint as to what I'm doing wrong in my control structure, this is a very basic skill, and though I am moving forward quickly, if I can't master this small thing, I certainly won't get very far :)

Thank you for your time, Perl Monks

Replies are listed 'Best First'.
Re: Control Structure problem, mistake can't be found
by broomduster (Priest) on Aug 23, 2008 at 00:21 UTC
    One more tip for how to debug this kind of thing....

    After you set $guess (whether you read it from STDIN or not), but before the line

    if ($guess eq $correct[$i])
    you can see what the value of $guess is by doing
    print "[$guess]";
    Note the square brackets ( [ and ] ). They are important here because they show where the value starts and ends. So if the value of $guess is Yellow\n, you get
    [Yellow ]
    and you see clearly that you have a \n at the end (which you may not want).
Re: Control Structure problem, mistake can't be found
by FunkyMonk (Chancellor) on Aug 22, 2008 at 23:08 UTC
    Where does the contents of $guess come from? If it's input from the user via the keyboard, or from a file, it's likely to have a newline after it. Try matching using $guess eq "$correct[$i]\n" or chomp your input before comparing.
Re: Control Structure problem, mistake can't be found
by gone2015 (Deacon) on Aug 23, 2008 at 00:33 UTC

    As a general point this game isn't going to be easy, even if you know the expected answers, the:

    if ($guess eq $correct[$i])
    is requiring the guess to have the exact capitalisation of the answer and won't tolerate extra white-space or any other deviation from the expected string. Related to that is what FunkyMonk has already suggested, which is the possible presence of a newline on the end of $guess. Preprocessing $guess and/or using a form of regular expression to do the matching will definitely help.

    In passing I notice:

    @incorrect = (@incorrect, $guess);
    which might be better written as:
    push @incorrect, $guess ;
    and also:
    my $grade = 100 - length(my @incorrect) * 33.3;
    which I rather doubt is what you meant. It sets $grade to 66.7, because length doesn't give the length of an array, it gives the length of a string in characters. So why does it give an answer at all when asked for the length of an array ?... well, length(my @incorrect) gives 1 because the value of my @incorrect is an empty array, which when evaluated in scalar context gives the number 0, which when converted to a string is '0', which is 1 character long... This is Perl all over :-)

    To get the number of entries in an array you just evaluate the array in scalar context... so: scalar(@incorrect) is probably what you wanted.


    Finally, at the risk of being presumptious, if you are new to programming in general, I'd really advise you to study another language.

    It's true that you can hack about in Perl and do some wonderful stuff quite economically. But, not only is it possible to pick up some truely disgusting habits, you need a grounding in something more formal if you are to really understand what Perl is doing.

    Look at it this way, compared to most languages English has apparently little structure and few rules. This makes it relatively easy to learn and use, at least to get by. However, it is also hard to really master the subtleties of English, with all its irreglarity, conventions, idioms and other oddities; and to do that you do need some grammer and some formal teaching. Perl is like that.

      ...not only is it possible to pick up some truely disgusting habits, you need a grounding in something more formal if you are to really understand what Perl is doing.

      ++, oshalla, for your other helpful advice, but not without taking exception to the quoted observation!

      I have no real quibble with your "possible" re "truly disgusting habits" (NB: in English there's no 'e' in 'truly.') except to note that's pretty much also "possible" to write effectively - if inelegantly - despite misspellings -- whether writing narrative or text. Conversly "proofs" of almost any proposition can be so badly conceived or structured as to be worthless despite having the appearance of proper form or even superficial plausibility.

      However, IMO, to suggest that it's not possible to understand "what Perl is doing" without formal study of another language for "grounding" is extreme, to say nothing of "unproven!"

        Yes, I can never correctly spell 'truly' (correction: I can seldom spell it). I am ashamed. What can I say: without the 'e' it just looks to me as if it's had an unfortunate accident with something sharp -- an object lesson in what happens when you run with scissors, perhaps ? It also looks almost as if it should be pronounced with a short 'u', but for that, I have to admit, two 'l's would be required -- as in 'cully'.

        Of course many other words that end in 'e' don't drop it when the adverb is formed -- 'insanely', 'obscenely', 'approximately', 'effectively'. But that may be because they don't end in two vowels. So there's 'brusquely' and 'opaquely', but I guess the 'u' isn't really a vowel in this context. What about 'eerily', and of course 'duly' -- the later is clearly of a piece with 'truly'. But, lest we conclude there's a simple rule here, there's 'vaguely' and 'freely' -- admittedly the sound of the 'ue' in 'vague' is quite different from that in 'true', which may (or may not) be significant. Similarly, in the case of 'free' it may be that the 'ee' is effectively a compound vowel ?

        English: what can I say ? Gosh, what a glorious mess !


        Anyway, returning to what I suppose is the real matter: I think that to "really understand what Perl is doing" you do need some more formal grounding -- highlighting the stress I intended on the "really".

        For instance, consider the following:

        my @rows = () ; while (my $line = <SHEET>) { chomp($line) ; my @columns = split /\s+/, $line ; push @rows, \@columns ; } ;
        What do we think is going on here ?
        1. declare the (one dimensional) array @rows and ensure it is initialised empty.
        2. run a while loop using various pieces of commonly used Perl magic to step line by line through the file referred to by SHEET, declaring the working variable $line for the duration of the while loop
        3. chomp($line) removes the line ending from the input line (unless you're running on a *nix type system and the file came from winders, in which case it tends to remove half the line ending, SO STAY AWAKE)
        4. declare the (one dimensional) array @columns for the remaining duration of the loop, and assign to it the result of splitting up the input line.
        5. push a reference to the array @columns onto the array @rows (we understand that in Perl arrays and lists are strongly related)
        The result is intended to be a two-dimensional array (or what passes for one in Perl) of cells. And, indeed that's what happens.

        Now, references are sophisticated, but just taking the code at face value, one might worry:

        • if @columns only exists for the duration of the enclosing block, what does the @rows array contain after the block ?
        • why does this work at all, given that it appears to push the same thing, \@columns, on to @rows each time around the loop ?
        So, we all know that this works because my @columns doesn't declare an array, it declares something that contains a pointer to a data structure which contains the actual array and which lives on a heap. The \@columns doesn't really return a reference to @columns, it returns a copy of the pointer it contains. What's more, each time around the while loop @columns is reset before the new split /\s+/, $line is assigned to it, so each \@columns is different and refers to a different data structure on the heap -- each of which persists after the block (leaving reference counting and garbage collection to one side, for the time being).

        Scanning back over the above, I observe it's hard to describe what's really going on, without using some fairly heavy computer science concepts. IMO, the study of more formal language(s) is the way to gain understanding of these and how they work.

        This is only one example, but I believe it illustrates a general point.

Re: Control Structure problem, mistake can't be found
by graff (Chancellor) on Aug 22, 2008 at 23:54 UTC
    My bet is with the first reply -- if the user is entering answers at the keyboard, perl is reading the final "\n" that has to be typed in order to complete each input, and you have to use "chomp" to get rid of that.

    As for making progress, you made the task harder for us (and less helpful for you) by showing us disjointed snippets and leaving things out. Next time, see if you can create a short script that can be run by itself and will demonstrate the problem you are having. For instance, your post could have been like this:

    Why is it that the script below always prints "huh?", even though I definitely provide the input that should produce a different output?
    #!/usr/bin/perl use strict; my @answers = ( 'me', 'you' ); print "Who needs more help, me or you? "; my $response = <STDIN>; if ( $response eq $answers[0] ) { print "Good luck with that.\n"; } elsif ( $response eq $answers[1] ) { print "So help me!\n"; } else { print "huh?\n"; }

    Given a situation like that, the vast majority of monks could tell you immediately, with no questions or doubt, that you need to "chomp" the input when reading from STDIN. (Or else they would tell you where to read about that in docs, tutorials, books, etc.)

Re: Control Structure problem, mistake can't be found
by GrandFather (Saint) on Aug 23, 2008 at 02:36 UTC

    If possible it is a really good idea to keep related data together so that it can be handled in a common fashion. Perl lets you do that by using structures like arrays of array and hashes etc. In your case it makes sense that you keep the question and answer pairs together. You could use an array for each pair, but it's easier to understand the code if you use a hash instead. Consider:

    use strict; use warnings; my @quiz = ( {ask => "Name the definitive rock band..", answer => "The Rolling Stones" }, {ask => "What is their best song?", answer => "Moonlight Mile" }, {ask => "What is their best album?", answer => "Sticky Fingers" } ); my @incorrect; for my $question (@quiz) { print "$question->{ask}: "; my $guess = <STDIN>; chomp $guess; if (lc $guess eq lc $question->{answer}) { print "You Rock!\n"; } else { print "You Suck!\n"; # if incorrect, reply and save answer to + be calculated push @incorrect, [$question, $guess]; } } my $grade = (@quiz - @incorrect) / @quiz * 100; print "Grade: $grade\n";

    Note that there is now no need for a counter to access the question and answer. Because each question and answer is paired it is trivial to add questions or change their order and know that the right answer is matched with each question.

    Note too that now the entire question and answer are stored along with the answer given in the incorrect list. Also the grade uses the count of elements in the two arrays for the calculation so that a manually calculate constant isn't required - the grade calculation remains correct if the number of questions changes.


    Perl reduces RSI - it saves typing
      Just wondering, this syntax:
      my @quiz = ( {ask => "Name the definitive rock band..", answer => "The Rolling Stones" }, {ask => "What is their best song?", answer => "Moonlight Mile" }, {ask => "What is their best album?", answer => "Sticky Fingers" } );
      it's object oriented Perl, right? Or is it just another way to give hash keys a value? I am only familiar with this type of syntax for the same thing:
      $myquiz{"question_here"} = "answer here";
      I've just never seen it done in this way, and I was wondering what the difference was (which when I looked it up, I thought I understood it to be "object oriented"), and if there is a benefit of chosing one over the other?

        Let's take it a little bit at a time:

        my @quiz = (...);

        initializes an array from a list.

        {...}

        generates a reference to a hash. A reference is a "handle" that you can use to refer to something else. So:

        my @quiz = ({...}, {...}, {...});

        generates an array containing references to three hashes. Each hash is of the form:

        {ask => "...", answer => "..."}

        The => is the "fat comma". It's exactly like an ordinary comma, except that it pretends the (non-white space) sequence of characters to its left has quotes around it. ask => "..." is the same as 'ask', "...". The fat comma is a handy way of generating a list to initialize a hash and is often used as a heads up to indicate that two items in a list are a pair.

        The intent is to generate a data structure comprised of an array containing a list of hashes where each hash represents a question and answer pair. Other information could be added to each hash such as a weighting for the question (so harder questions are worth more) for example:

        my @quiz = ( {ask => "Name the definitive rock band..", answer => "The Rolling Stones", value => 10, }, {ask => "What is their best song?", answer => "Moonlight Mile" value => 20, }, {ask => "What is their best album?", answer => "Sticky Fingers" value => 5, }, );

        Perl reduces RSI - it saves typing
        No, it's not OO perl. It's just declaring an array of hashrefs, or as commonly known, an array of hashes aka an AoH. See perldsc for more on this.

        For object oriented programming in Perl, you going to need modules (see perlmod) and bless. If you're interested in OOP, you should probably look at perlboot and perltoot before anything else.

Re: Control Structure problem, mistake can't be found
by eosbuddy (Scribe) on Aug 22, 2008 at 23:22 UTC
    It seems to work for me (cause I artificially made it to work). Some possible bugs are that your increment $i++; works only if expression is true - but this maybe what you intend to do as well. It would be helpful if you could post some errors:
    #!/usr/bin/perl use strict; use warnings; my @incorrect; my @correct = qw / 1 2 3 4 5 6 7 8 9 10 /; my $guess = 1; my $i = 0; if ($guess eq $correct[$i]) { # if guess is correct, reply print "You Rock!\n"; $i++; } else { print "You Suck!\n"; # if incorrect, reply and save answer to be calcu +lated @incorrect = (@incorrect, $guess); }
      I do have
      chomp($guess = <STDIN>);
      at the top, before the piece I posted. When I run the program as is, this is my output:
      Name the definitive rock band.. The Who You Suck! What is their best song? Moonlight Mile You Suck! What is their best album? Sticky Fingers You Suck! You Rock!
      As you can see, no matter correct, or incorrect, always get "You Suck", then I have to enter an extra \n at the end to end the loop, after that, three lines later, it says "You Rock!, for no reason whatsoever. So, here's the whole code:
      #!usr/bin/perl use strict; my @questions = ("Name the definitive rock band.." , "What is their be +st song?" , "What is their best album?"); my @correct = ("The Rolling Stones" , "Moonlight Mile" , "Sticky Finge +rs"); my $grade = 100 - length(my @incorrect) * 33.3; my @incorrect; my $i = 0; my $guess; # This program asks user three questions, then grades the user's ans +wers. while ($i <= scalar(@incorrect)) { # loop through questions print $questions[$i] . "\n"; chomp($guess = <STDIN>); $i++; if ($guess eq $correct[$i]) { # if guess is correct, reply print "You Rock!\n"; $i++; } else { print "You Suck!\n"; # if guess is incorrect, reply and save +answer to be calculated @incorrect = (@incorrect, $guess); #while ($guess ne @correct[$i]) { # if guess is incorrect, pro +mpt user to try again # print "Try again!\n"; # chomp($guess = <STDIN>); # $i++; # } # end while } # end if } # end while print $grade . "\n"; # calculate and print user's grade print "\nRockstar Programming Inc. All Rights Reserved";

      Note the loop commented out at bottom is a whole other topic, just included it so ya'll could see, because I certainly don't understand this output at all.

      Also, eosbuddy, is this, I'm assuming a regular expression?

      my @correct = qw / 1 2 3 4 5 6 7 8 9 10 /;
      I do know what they are, lol, just not used to seeing that type at this point. I can't use them, but it is helpful to see how you put my data together to make it work.

      But, does anyone understand this output I'm getting? I know my knowledge is very limited, but I just can't seem to place my mistake. There are, I'm sure several re-writes I could do, but I really want to understand why this won't work, to further my skill in the language, because theoretically I really think it should??

      Also, please note that my output includes me entering answers as the user, I entered correct and incorrect to show that it didn't change the response.

      Also, I am very much aware of how useless a trivia game is written this way, especially without any regular expressions. It's more of a learning exersise, to give me a small example of doing something on my own in Perl, and I also do know a bit more these days, but regardless of the fact that it is a useless game lol, I want to understand my mistake more for the experience than the game :)

        Your problem is here:
        $i++; if ($guess eq $correct[$i]) { # if guess is correct, reply
        Array indices in Perl start with 0. You are incrementing $i before you use it as an index to check the first answer. So the "correct" answer to the first question you ask should be "Moonlight Mile" ... ;-)
        You are suffering from premature incrementation :)

        And, for goodness' sake, use warnings;. This would have shown you that you incremented past the end of your @correct array (the first index of an array in perl is 0, not 1). I added these print statements to show the problem:

        print "guess=$guess=\n"; print "correct[$i]=$correct[$i]=\n"; if ($guess eq $correct[$i]) { # if guess is correct, reply print "You Rock!\n"; $i++;

        broomduster and toolic provided the solution, I just want to emphasize what broomduster said in another post in this thread: Learn to use print to debug your program.

        If you had printed both of the comparision operands, $guess and $correct[$i] (and whatever else seemed interesting), you could have solved the riddle by yourself. And as you surely know, you best learn from things you find out yourself.

        AAHHH, I see said the blind man! Thank you so much! lol, I really had a sneaky suspicion that is was a trivial problem, but I just couldn't find it. By the way, I can't use warnings at the moment, I'm on windows, and coding in editpad with the Perl interpreter, and my version is 5.0, so it doesn't pick it up :\

        Anyways, thank you all so much for replying and helping me over that hump :)

        As has already been pointed out by oshalla, you don't want to use length on an array, but rather scalar. A few other important notes:
        • Every time you my a variable, you get a fresh variable. This means that you (almost) never want to use two mys in the same scope. For example, if you typed
          my $a = 1; my $a;
          then you would find that $a was uninitialised.
        • qw// is a quoting operator that takes a string and splits it on whitespace, putting the result into an array. In our case, qw/1 2 3 4/ evaluates to the array (1, 2, 3, 4). The reason that they make you think of regular expressions is that /EXPR/ is short for m/EXPR/, the matching operator. The slashes (in qw//, m//, and other quote-like operators) can be changed to any of (), [], {}, or any repeated delimiter that you choose (like qw+1 2 3 4+)—although be careful if you use '' or ##.
        • @array = (@array, $new_entry) is more idiomatically written as push(@array, $new_entry). See push.

        qw> is the "quote words" operator. It lets you do nifty stuff like:

        my @foo = qw( key west florida orange juice beverage ); # this is identical to: my @foo1 = ( 'key', 'west', 'florida', 'orange', 'juice', 'beverage', );

        See perlop, specifically, the section on Quote and Quote-like Operators" for more info.

        You should consider using push instead of recopying the array:

        # @incorrect = (@incorrect, $guess); push @incorrect, $guess;

        When I was first learning, the single most useful piece of the documentation was the list of functions by category in perlfunc--although I used the version in my copy of Perl in a Nutshell. Anytime I felt like I was doing something awkward or that there might be a better way to do something, I'd look for a built in function to accomplish it.

        Useless little toy programs are an excellent way to start. At the beginning of a big project I'll usually build several useless, little toy programs to test my ideas--only I've learned to call the toys "prototypes", because that sounds better. As a beginner, you are building the prototypes for ALL your future programs.


        TGI says moo

Re: Control Structure problem, mistake can't be found
by Anonymous Monk on Aug 23, 2008 at 07:34 UTC
    I have checked and double-checked, in several test routines, that all of my variables hold the correct values, so I know the problem is not there.

    Sometimes terminals can fool your checks