Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Passing input through a hash then displaying results

by brayshakes (Acolyte)
on Jan 23, 2007 at 06:56 UTC ( [id://596036]=perlquestion: print w/replies, xml ) Need Help??

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

I am writing a script titled, "grades.pl." Essentially, it takes user input and returns a letter (A,B,C,D,F). My problem is that I don't know how to loop the variable, $grade, through the hash keys so that the input will return the corresponding value.
$grade = <STDIN>; %scores=( "$grades<=90" => "A", "$grade<=80" => "B", "$grade<=70" => "C", "$grade<=80" => "D", "$grade>=50" => "F"); foreach $grade (%scores) { if ( $grade != key ) {
Am I close?

Replies are listed 'Best First'.
Re: Passing input through a hash then displaying results
by ikegami (Patriarch) on Jan 23, 2007 at 07:06 UTC

    Am I close?

    So close, yet so far away.

    • I notice a lot of undeclared variables. Adding use strict; will show these errors. Adding use warnings; will also help you find errors.
    • How do you plan on executing the expression? While it's possible, your approach is fundamentally flawed.
    • %scores flattens to a key-value-key-value-... list. You probably want keys(%scores).
    • The keys of a hash are returned in an unpredictable order, yet you assume they are returned in the order in which they were added to the hash.
    • $grade contains an undesireable trailing newline.
    • != is used for numerical comparisons. "43>=50" is not a number.
    • $grade is used for two different purposes.
    • You variable names are wonky. $grade contains a score and %scores is used to lookup grades.
    • You table is wonky. "D" the same as "B". What if the score is 92? What if the score is 46?

    Update: I had kept my solution hidden since it sounded like a homework question. Since others have posted theirs, I'll reveal mine:

    use strict; use warnings; my %grade_lookup = ( 90 => "A", 80 => "B", 70 => "C", 60 => "D", ); my $score = <STDIN>; chomp($score); my $grade = "F"; foreach my $gate_score (sort { $a <=> $b } keys %grade_lookup) { if ( $score >= $gate_score ) { $grade = $grade_lookup{$gate_score}; } } print("$score is $grade\n");

    But since we want an ordered lookup table, why not use an array?

    use strict; use warnings; my @grade_lookup = reverse( [ 90 => "A" ], [ 80 => "B" ], [ 70 => "C" ], [ 60 => "D" ], ); my $score = <STDIN>; chomp($score); my $grade = "F"; foreach (@grade_lookup) { if ( $score >= $_->[0] ) { $grade = $_->[1]; } } print("$score is $grade\n");
      #!/usr/bin/perl print "Enter the character\n"; $grade = <STDIN>; chomp($grade); %scores=( 90 => "A", 80 => "B", 70 => "C", 60 => "D", 50 => "F" ); if($grade >= 90) { print $scores{90}; } if($grade < 90 && $grade >= 80 ) { print $scores{80}; } elsif($grade < 80 && $grade >= 70 ) {print "hai"; print $scores{70}; } elsif($grade < 70 && $grade >= 60 ) { print $scores{60}; } elsif($grade < 60 && $grade >= 50 ) { print $scores{50}; }
      try this code
Re: Passing input through a hash then displaying results
by shmem (Chancellor) on Jan 23, 2007 at 09:54 UTC

    Close, yes, for some value of close ;-) Move the comparison into a loop.

    Conditionals as keys to a hash don't work. Hash keys are strings. Ok, you have your conditions inside double quotes, and $grade is defined (but you should chomp it, since it contains a trailing newline), so you end up with a string that could be evaluated with eval. But what if "wibble" was entered? You would end up having

    %scores = ( "wibble<=90" => "A", ... );

    which isn't a valid comparison. You need to check the content of $grade first.

    It seems from your code that you want to have the comparisons evaluated in the order they are listed. You can't operate directly on a hash for that without bringing the keys into order, since a hash's elements aren't necessarily (in fact, unlikely) returned in the order they were entered.

    $grade = <STDIN>; ... foreach $grade (%scores) {

    Here you are masking the $grade variable with a temporary loop variable. No way to get at what you've read from STDIN. And you are iterating over a flat list of key/value pairs.

    If you want to iterate over the keys of your hash, use

    foreach my $key (keys %hash) { ... }

    To iterate over the values, use

    foreach my $value (values %hash) { ... }

    One approach for your problem is reversing your hash and using anonymous subroutines as values:

    chomp (my $grade = <STDIN>); $grade !~ /^\d+$/ and die "Input must be numeric!\n"; my %scores = ( A => sub { return $_[0] <= 90 ? 1 : 0 }, B => sub { return $_[0] <= 80 ? 1 : 0 }, C => sub { return $_[0] <= 70 ? 1 : 0 }, D => sub { return $_[0] <= 60 ? 1 : 0 }, # just a guess, otherwise + like 'B' F => sub { return $_[0] >= 50 ? 1 : 0 }, # really? shouldn't that +be '<=' ? ); my $result; foreach my $key (qw(D C B A F)) { if ($scores{$key}->($grade) ) { $result = $key; last; } } print $result,$/;

    Note that 'F' is never returned, since for all  $grade <= 60 'D' will be returned. If you evaluate 'F' up front, none of A - D will ever be returned.

    So maybe you have a typo, and you really mean your comparisons to be identical for all keys? That would simplify things, no need for anonymous subs, no need to reverse the hash:

    chomp (my $grade = <STDIN>); $grade !~ /^\d+$/ and die "Input must be numeric!\n"; # hash with upper threshold as keys my %scores = ( 90 => 'A', 80 => 'B', 70 => 'C', 60 => 'D', 50 => 'F', ); my $result; foreach my $key (sort keys %scores) { if ($grade <= $key) { $result = $scores{$key}; last; } } print $result, $/;

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Passing input through a hash then displaying results
by f00li5h (Chaplain) on Jan 23, 2007 at 11:45 UTC

    You could use a neat little table

    print $_, $_ > 90 ? 'A' : $_ > 80 ? 'B' : $_ > 70 ? 'C' : $_ > 60 ? 'D' : 'F' , "\n" for 89,90,91, 80, 70 , 50;

    and if you wanted a hash, you could simply

    my %grade_for = map { $_ => $_ > 90 ? 'A' : $_ > 80 ? 'B' : $_ > 70 ? 'C' : $_ > 60 ? 'D' : 'F' } for 0..100;
    ... later
    $student_name = 'f00li5h'; $student_score = 61; print "$student_name scored $grade_for{$student_score}!"
    here I've used map to create a lookup. My %grade_for is the same as your %scores, although perhaps an array would have worked just as well here (due to the non-sparse numeric keys)

    f00li5h nudges the grammar

    @_=qw; ask f00li5h to appear and remain for a moment of pretend better than a lifetime;;s;;@_[map hex,split'',B204316D8C2A4516DE];;y/05/os/&print;
Re: Passing input through a hash then displaying results
by rkrieger (Friar) on Jan 23, 2007 at 14:13 UTC

    You'd probably make things easier for yourself if you determine how you want to deal with grades between the multiples you defined. From that point on, you can just look up the grade in a table.

    Assumptions:
    • You do not deal out grades below 50 or above 90 or are willing to accept warnings about such grades.
    • You do not deal out a 'default' grade (e.g. F)
    • You only deal with thresholds in multiples of 10.
    Caveats:
    • 85 etc. get rounded down to 80, even in _round_grade(). Perhaps the POSIX:: functions are more to your liking. See the Perl documentation for sprintf for more info.
    • For non-existent grades, you get a warning and _convert_grade() returns an empty string.
    Code suggestion:
    #!/usr/bin/perl use strict; use warnings; use Carp; # Table containing numerical grade thresholds my %grade_for = ( '90' => 'A', '80' => 'B', '70' => 'C', '60' => 'D', '50' => 'F', ); # Determine whether you want to round up or 'chop off' grades sub _round_grade { my $grade = shift; # We deal with thresholds in multiples of ten return 10 * sprintf( "%02.0f", $grade / 10 ); } sub _chop_grade { my $grade = shift; # We deal with thresholds in multiples of ten return 10 * int ( $grade / 10 ); } sub _convert_grade { my $grade = shift; # Check whether the grade entered is listed in our table if ( defined( $grade_for{$grade} ) ) { return $grade_for{$grade}; } else { # No character representation available; warn the user carp "Character grade for $grade not listed in our table."; return q{}; } } LINE: while ( my $line = <STDIN> ) { # Strip the newlines from our input chomp $line; # Prepare a RE to match the line against my $grade_re = qr{ \A # Start of the string \s* # Allow for leading spacing (\d{1,2}) # Match at least 1 digit and at most 2 digits \s* # Allow for trailing spacing \z # End of the string }xms; # Skip lines that are not a number next LINE if $line !~ m{$grade_re}xms; # Be strict when dealing out grades; round down our numerical inpu +t # $1 is valid, given the previous RE match my $grade_numeric = _chop_grade($line); # Convert our numeric input to a character my $grade = _convert_grade($grade_numeric); print "$line - $grade\n"; }
    Debugging:
    foreach my $grade ( 4, 8, 45, 48, 50, 51, 57, 60, 64, 66 , 90, 91, 99, + 100) { my $chopped = _chop_grade($grade); my $rounded = _round_grade($grade); my $grade_chopped = _convert_grade($chopped); my $grade_rounded = _convert_grade($rounded); print "${grade}: ${rounded}/${grade_rounded} (Rounded) " . "- ${chopped}/${grade_chopped} (Chopped)\n"; }
    --
    If you don't know where you're going, any road will get you there.
Re: Passing input through a hash then displaying results
by logie17 (Friar) on Jan 23, 2007 at 07:10 UTC
    You probably would want to do something like the following:
    my $grade = <STDIN>; my %scores = ( "90" => "A", "80" => "B", "70" => "C", "60" => "D", "50" => "F"); my $letter_score = $scores{$grade}; if ($letter_score eq "A"){ #return this }

    There is really no need to loop to lookup key/value pairs. I hope this helps. If you're trying to execute statements you may want to look into the eval function.
    s;;5776?12321=10609$d=9409:12100$xx;;s;(\d*);push @_,$1;eg;map{print chr(sqrt($_))."\n"} @_;
      What if he scored 89? That's why the looping is needed ( if you use a hash ).
        I would think for such a simple program no need to loop, but just simple if control statements would suffice.
        s;;5776?12321=10609$d=9409:12100$xx;;s;(\d*);push @_,$1;eg;map{print chr(sqrt($_))."\n"} @_;

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://596036]
Approved by ikegami
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others taking refuge in the Monastery: (6)
As of 2024-03-29 13:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found