Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw

Printing keys from a hash by value?

by jms53 (Monk)
on Jan 30, 2012 at 14:31 UTC ( #950781=perlquestion: print w/replies, xml ) Need Help??
jms53 has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks, I have a hash table from which I would like to print 20 keys with the highest values. I currently have this code:

my %top_words = reverse %words #reverse to sort by value as opposed to key %top_words = sort {$top_words{$b} <=> $top_words{$a}} %top_words; # so +rt z-a to get the highest ones at the top my $counter=0; foreach my $value (%top_words) { if ($counter > 20) {last;} #exit loop print "$top_words{$value}\n"; $counter++; } #print 20 words with highest value

This returns numbers, and the following error message:

 Use of uninitialized value within %top_words in concatenation (.) or string at ./ line 114.
I can't see the error, and line 114 is the print command. Thank you.

Replies are listed 'Best First'.
Re: Printing keys from a hash by value?
by Corion (Pope) on Jan 30, 2012 at 14:40 UTC
Re: Printing keys from a hash by value?
by ricDeez (Scribe) on Jan 30, 2012 at 15:17 UTC

    You are missing the keyword keys before %top_words as it has already been pointed out. Also as suggested by using an explicit reverse rather than swapping $a and $b you make your intentions clearer.

    Rather than using the loop, you can just use an array slice as shown in the sample code below:

    use warnings; use strict; use Smart::Comments; my %words = (A => 440, B => 765, C => 369, D => 788, E => 647, F => 216, G => 788, H => 871, I => 733, J => 947, K => 471, L => 875, M => 982, N => 314, O => 564, P => 178, Q => 212, R => 487, S => 768, T => 939, U => 410, V => 753, W => 765, X => 413, Y => 201, Z => 233, AA => 553, AB => 601, AC => 267, AD => 988, AE => 140, AF => 784, AG => 667, AH => 987, AI => 460, AJ => 186, AK => 592, AL => 835, AM => 427, AN => 28, AO => 214, AP => 912, AQ => 167, AR => 421, AS => 400, AT => 583, AU => 250, AV => 248, AW => 377, AX => 850, AY => 862, AZ => 430, AAA => 269, AAB => 656, AAC => 30, AAD => 124, AAE => 444, AAF => 598, AAG => 915, AAH => 276, AAI => 9, AAJ => 831, AAK => 522, AAL => 753, AAM => 799, AAN => 777, AAO => 330); my @top_words = (reverse sort { $words{$a} <=> $words{$b} } keys %words) [0 .. 19 ]; # Not [1 .. 20]!!! ### @top_words

      thank you very much!!

      Not only does it work, but it runs so much more quickly than sorting the couple thousand entries I have to analyze in a spreadsheet.

      Minor correction on my previous post... the slice should have been 0 .. 19

Re: Printing keys from a hash by value?
by aaron_baugher (Curate) on Jan 30, 2012 at 15:58 UTC

    That's a pretty unusual way to use reverse. You'll get away with it as long as your values are all unique, but if any two values are the same, one will get clobbered. Also, since you swapped the keys and values that way before doing your sort, your sort is actually sorting by your words, which used to be your keys and are now your values.

    So in my opinion, reversing the hash, even if you know your values are unique, is confusing and unnecessary. Just sort your original hash by the values, and stop after the 20th one.

    my %top_words; $top_words{chr(64+$_)x4} = $_ for (1..26); # fill hash with some word/ +number pairs my $counter = 1; for my $word (sort {$top_words{$b} <=> $top_words{$a}} keys %top_words +){ print "$word\n"; last if $counter++ == 20; }

    Aaron B.
    My Woefully Neglected Blog, where I occasionally mention Perl.

      I'm confused! How is sort {$top_words{$b} <=> $top_words{$a}} different from reverse sort {$top_words{$a} <=> $top_words{$b}}?

      Also how are the values being clobbered? I would understand this if we were 'reversing' the hash by creating a new hash where the values of the original hash were the keys of the derived hash. However I applied a reverse to a sort based on the values in the original hash. This is different from something like  my %top_words = reverse %words;

        It's not different, but that's not what he did. First he passed his hash, which presumably had words as keys and numbers as values, to reverse, which happily treated it as a list and reversed the list, which he assigned to another hash, turning the reversed list back into a hash. For example:

        my %hash = ('one' => 1, 'two' => 2, 'three' => 3, 'four-minus-one' => +3); my %newhash = reverse %hash; # %newhash now contains (1 => 'one', 2 => 'two', 3 => 'four-minus-one' +) # or # (1 => 'one', 2 => 'two', 3 => 'three')

        See, it turned the original hash into a list and reversed it. Since the last item in the list was a value, that became the first item in the reversed list, becoming a key, with the next-to-last item in the original list (that value's key) becoming its value, and so on. And if any value appeared twice in the original list, it can't appear more than once as a key in the new hash, so all but one of its matching keys will be clobbered. Also, you can't know which key/value pairs will get clobbered, since a hash is unordered by definition.

        Aaron B.
        My Woefully Neglected Blog, where I occasionally mention Perl.

      That is very interesting. I can surely use this to benchmark the different algorithms.

      However, could you enlighten me on how the first part works? perldoc has nothing on chr... Thanks!

        chr gives the character corresponding to an ASCII value. 65 = 'A', 66 = 'B', and so on. In retrospect, I could have made my loop 65..90, but I think 1..26 makes it more obvious that I'm spanning the alphabet. Putting x4 after it just turns 'A' into 'AAAA' and so on, to make it more obvious that they're words.

        Aaron B.
        My Woefully Neglected Blog, where I occasionally mention Perl.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://950781]
Approved by Corion
[talexb]: Wow, what hilariously bad form.
[SuicideJunkie]: Just wait; someday soon, you'll be given a DB with unicode emojis in the column names.
[Corion]: marinersk: Well, I have done select statements like select sum(foo) as "Total Amount", ..., but to have a table like that makes me shudder
[Corion]: SuicideJunkie: :-D
[marinersk]: SuicideJunkie LOL
[choroba]: Woohoo! Fixed a test that hasn't run for 3 years.
[marinersk]: Corion Yes, sometimes whitespace in column headers is acceptable, but I still consider it be less than desireable if that query might get revectored for an ETL-esque process...
[marinersk]: choroba++

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (10)
As of 2017-05-25 15:05 GMT
Find Nodes?
    Voting Booth?