Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number

Sorting Keys and Values

by mt2k (Hermit)
on Apr 27, 2002 at 09:02 UTC ( #162492=perlquestion: print w/replies, xml ) Need Help??
mt2k has asked for the wisdom of the Perl Monks concerning the following question:

Okay, I know that questions *similar* to this have been answered here before.
But mine requires what I believe is one more step that I cannot achieve with my peanut of a brain :)

Basically, I have the following hash:

%db = ( 'scores' => { 'user1' => 200, 'user2' => 190, 'user3' => 232, 'user4' => 187, 'user5' => 190 } );

This hash is a list of scores that users have obtained at a certain activity. What I want to do with this hash is print out a "trophy stand" type of output, that shows the users in a list from highest score to lowest and their scores. All users need to be placed in their proper position (such as 1st and 2nd place). The hash is much bigger than this. Pretend there are at least 30 users in the hash. Some of this is simple to do, but there is one thing I could not bypass: the exception of multiple users who have the same score.

What I have right now basically outputs my example hash as:

1st Place: user3 (232 Points) 2nd Place: user1 (200 Points) 3rd Place: user2 (190 Points) 4th Place: user5 (190 Points) 5th Place: user4 (187 Points)
What the output should really look like is this, where tied scores are put at the same level:

1st Place: user3 (232 Points) 2nd Place: user1 (200 Points) 3rd Place: user2 (190 Points), user5 (190 Points) 4th Place: user4 (187 Points)

Here is what I have for code:

my @totals = sort { $db{'scores'}{$b} <=> $db{'scores'}{$a} } keys(%{$ +db{'scores'}}); #Right here I need something that places all the users and their score +s in another hash #This new hash would probably be a hash of arrays. #I just can't figure it out! #Also, these lines here should not be hardcoded! #I do not know how many positions there are! #Meaning I manually outputted places 1st through to 5th, #but there might be 25 of them! #There should be some kind of loop that does it for me print qq| 1st Place: $totals[0] 2nd Place: $totals[1] 3rd Place: $totals[2] 4th Place: $totals[3] 5th Place: $totals[4] |;

I hope I was able to make myself clear on what I am asking for.
If anyone can get this for me, I will love you to death!
Yes, I am capable of loving someone so much that they die :)

Replies are listed 'Best First'.
Re: Sorting Keys and Values
by vladb (Vicar) on Apr 27, 2002 at 09:23 UTC
    This works for me:
    use strict; my %db = ( 'scores' => { 'user1' => 200, 'user2' => 190, 'user3' => 232, 'user4' => 187, 'user5' => 190 } ); my %sorted_scores; # # Generate another hash structure where # scores are used for keys and values # hold list of users who got that score. # # hash is a wonderful thing ;-) # for (keys %{$db{'scores'}}) { push @{$sorted_scores{$db{'scores'}{$_}}}, $_; } my $i = 1; for my $score (sort {$b <=> $a} keys %sorted_scores) { print "$i Place: "; for (@{$sorted_scores{$score}}) { print "$_ ($score), "; } print "\n"; $i++; }
    This code produces the following output:
    1 Place: user3 (232), 2 Place: user1 (200), 3 Place: user2 (190), user5 (190), 4 Place: user4 (187),
    The only remaining part is to remove excessive commas and also print numbers in a slightly different format... (adding 'st' to 1 and 'nd' to 2 etc.)

    Cheers, vladb.

    "There is no system but GNU, and Linux is one of its kernels." -- Confession of Faith
Re: Sorting Keys and Values
by abstracts (Hermit) on Apr 27, 2002 at 09:28 UTC
    Here is one way to do it: make a hash of arrays of users who have the same score. Sort the keys, print, ta ta.
    #!/usr/bin/perl -w %db = ( 'scores' => { 'user1' => 200, 'user2' => 190, 'user3' => 232, 'user4' => 187, 'user5' => 190 } ); my %scores; while (my ($u,$s) = each %{$db{'scores'}}){ push @{$scores{$s}}, $u; } my $i = 1; for(sort {$b <=> $a} keys %scores){ local $"=", "; print "$i Place: ($_ Points) @{$scores{$_}}\n"; $i++; } __END__ 1 Place: (232 Points) user3 2 Place: (200 Points) user1 3 Place: (190 Points) user2, user5 4 Place: (187 Points) user4
    PS: output slightly different

      Taking a lead from Corion, if you did want sports rankings (and without additional modules), just change $i++ to ...

      $i += @{$scores{$_}};

      ... which will automatically make the next available rank skip N ranks if you have tied users. (ie, 1st, 2nd, 3rd, 5th assuming two people ranked 3rd.)


      Thank you so much abstract! Thank you too vladb! I am using abstracts' code for a couple reasons: 1. Short 2. No excess commas 3. Just looks nicer in all :)
Re: Sorting Keys and Values
by Corion (Pope) on Apr 27, 2002 at 09:30 UTC

    If you rather want to have a "sports" ranking, there is List::Ranking by miyagawa - if you have two people on place two, the next place handed out will be place number four.

    perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web
Re: Sorting Keys and Values
by powerman (Friar) on Apr 27, 2002 at 12:57 UTC
    Less code - less troubles, a lot of code - a lot of... ;-)
    #!/usr/bin/perl %db = ( 'scores' => { 'user1' => 200, 'user2' => 190, 'user3' => 232, 'user4' => 187, 'user5' => 190, }, ); printf "%02d Place: (%d Points) %s\n", ++$i, $_, join(", ", split " ", $users{$_}) for reverse sort keys %{{ map { $db{scores}{$_} => $users{$db{scores}{$_}}.=" $_" } keys %{$db{scores}} }}; __END__ 01 Place: (232 Points) user3 02 Place: (200 Points) user1 03 Place: (190 Points) user2, user5 04 Place: (187 Points) user4
Re: Sorting Keys and Values
by Maclir (Curate) on Apr 27, 2002 at 14:11 UTC
    Before you get too carried away, you need to correct some business logic first. You say the following is your desired output:
    1st Place: user3 (232 Points)
    2nd Place: user1 (200 Points)
    3rd Place: user2 (190 Points), user5 (190 Points)
    4th Place: user4 (187 Points)

    What you have to do is where two (or more) people tie for a place, you have to skip that number of places for the next place. Thus in the above situation you have no "4th place"; rather, user 4 is in 5th place. Of course, had three people tied for 3rd place, then the next place awarded would be 6th place.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://162492]
Approved by mt2k
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (7)
As of 2017-07-21 16:12 GMT
Find Nodes?
    Voting Booth?
    I came, I saw, I ...

    Results (328 votes). Check out past polls.