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

Condensed rank table output

by tlk (Acolyte)
on Sep 07, 2019 at 18:38 UTC ( [id://11105802]=perlquestion: print w/replies, xml ) Need Help??

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

Hello to you oh wise monks at the gates of the Na Pali temple!

I have a fairly simple %hash: UserName => score

eg:

Bob - 10 Alice - 12 Mike - 11 ... and so on.
I'd like to output it like this:
1. UserName - score 2. Another UserName - score ...
reverse sorted by user's tally obviously.

But there's a catch - what I need is to group the users with the same tally on a single row like this:

... 2. Alec - 14 3-5. Sarah, Jacob, Donny - 13 6. Alice - 12 ...

What would be the most appropriate way to go about?

Thank you in advance

Replies are listed 'Best First'.
Re: Condensed rank table output
by LanX (Saint) on Sep 07, 2019 at 20:08 UTC
    You want code or guidance? :)

    First build a hash of arrays with names of same score.

    $score{13} = [ "Sarah", "Jacob", "Donny" ]

    Than sort this hash.

    When printing add the size of the arrays to get the ranks.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      Good shout, thx.

      PS guidance is OK :)
        > guidance is OK :)

        and here some code to correct the other contributions ;-)

        I also added some printf to prettify the output. (though it might be easier to output the score before the names)

        use strict; use warnings; #use Data::Dump qw/pp dd/; # https://perlmonks.org/?node_id=11105802 # data stolen from tybaldt my %score_by_name = ( Alec => 14, Alice => 12, Bob => 8, Donny => 13, Jacob => 13, Mike => 11, Sarah => 13, First => 17 ); # invert %score_by_name to HoA my %names_by_score; while ( my ($name,$score) = each %score_by_name) { push @{$names_by_score{$score}}, $name; } #pp \%names_by_score; my $rank=0; for my $score ( sort {$b <=> $a} keys %names_by_score ) { my @names = @{$names_by_score{$score}}; # calculate ranks my $rank_str = $rank+1; $rank += @names; $rank_str .= "-$rank" if @names >1; # span if necessary # output printf "%3s. %-20s - %2d\n", $rank_str, join (", ",@names), $score; # printf "%3s. (%2d) %s\n", # $rank_str, $score, join (", ",@names); }

        1. First - 17 2. Alec - 14 3-5. Donny, Jacob, Sarah - 13 6. Alice - 12 7. Mike - 11 8. Bob - 8

        alternative output

        1. (17) First 2. (14) Alec 3-5. (13) Sarah, Donny, Jacob 6. (12) Alice 7. (11) Mike 8. ( 8) Bob

        updates
        • prettified variable names
        • fixed bug with lexical sort
        • added alternative output

      • inverted sort makes reverse obsolete

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Condensed rank table output
by davido (Cardinal) on Sep 07, 2019 at 21:34 UTC

    print "$_ => $hash{$_}\n" for sort {$hash{$b} <=> $hash{$a} || $a cmp +$b} keys %hash;

    Sort in reverse numerical order, and in cases of collisions, in ASCIIbetical order for names. If you wish to be nice and use proper Unicode collation for peoples' names, use Unicode::Collate for the part of the sort routine that comes after the || operator.

    use strict; use warnings; use Unicode::Collate; use utf8; binmode STDOUT, ':encoding(UTF-8)'; my $collator = Unicode::Collate->new(); my %hash = ( 'James' => 43, 'John' => 2, 'Jeffrey' => 18, 'Jamie' => 19, 'Joseph' => 43, '&#308;an' => 43, # Was latin capital letter j with circumflex be +fore PerlMonks got a hold of it 'Janice' => 12, 'Jasmin' => 7, 'Justin' => 31, 'José' => 43, 'Jordan' => 43, ); print "$_ => $hash{$_}\n" for sort {$hash{$b} <=> $hash{$a} || $collator->cmp($a,$b)} keys % +hash;

    (Sorry about PerlMonks breaking latin capital letter j with circumflex)


    Dave

Re: Condensed rank table output
by tybalt89 (Monsignor) on Sep 07, 2019 at 23:57 UTC
    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11105802 use warnings; my %hash = ( Alec => 14, Alice => 12, Bob => 10, Donny => 13, Jacob => 13, Mike => 11, Sarah => 13, First => 17 ); my $rank = 0; $_ = join '', sort {$b =~ s/\D+//gr <=> $a =~ s/\D+//gr } map " $_ - $hash{$_}\n", sort keys %hash; 1 while s/(.*) - (\d+)\n(.* \2\n)/$1,$3/; s/^(?=(.+))/$1 =~ tr~,~~ < 1 ? ++$rank . '.' : ++$rank . '-' . ($rank += $1 =~ tr~,~~) . '.' /gem; print;

    Outputs

    1. First - 17 2. Alec - 14 3-5. Donny, Jacob, Sarah - 13 6. Alice - 12 7. Mike - 11 8. Bob - 10
      Cheers for a true Perl-y way))
        I disagree with your assessment.
        I much prefer Rolf's code at Re^3: Condensed rank table output. That code has a very logical flow, is easy to understand and therefore easy to modify. Perl can be incredibly cryptic. I do not advise that except for Golf. Also be advised that "fewer lines of code" does not always equal "faster executing code". Often straightforward code runs faster.
Re: Condensed rank table output
by jcb (Parson) on Sep 07, 2019 at 21:12 UTC

    Running with LanX's advice: (untested)

    # Assumes %user_scores is map: UserName => score my %users_by_score = (); push @{$users_by_score{$user_scores{$_}}}, $_ for keys %user_scores; my $rank = 1; foreach my $rank_score (reverse sort keys %users_by_score) { print $rank++, '. ', join(', ', @{$users_by_score{$rank_score}}), +' - ', $rank_score, "\n" }

    This was a nice warm-up for me today, thanks. Read perldata, perlref, and perldsc for more on how this works.

      Almost! :)

      For this output 3-5 you'll need to "add the size of the arrays to get the ranks"

      Something like

      print $rank+1 ,'-', $rank+=@arr;

      should do. Also untested ;)

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

      Sidenote since I had the same bug.

      sort is by default alphabetic, when sorting numbers you need to provide a <=> clause to avoid a 1,11,2,21 order.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

        yep, noticed that, but decided it was too trivial to point out

        probably should've for the other seekers' sake)

        I've ended up using jcb's code, with your addition to get the \d-\d thingy.

        Thanks to you both, as well as to all the other monks! It's beautiful how a rather trivial q can enlighten you at the Monastery.

Re: Condensed rank table output
by Lotus1 (Vicar) on Sep 07, 2019 at 22:38 UTC
Re: Condensed rank table output
by BillKSmith (Monsignor) on Sep 09, 2019 at 14:34 UTC
    This is not a new problem. You must 'invert' your hash. A super search of this forum for 'invert hash' discovers several solutions. The book Perl Cookbook has a 'recipe' on the subject. Your issue of printing the resulting hash in the correct order is covered in the FAQ.
    perldoc -q "How do I sort a hash"
    Bill
Re: Condensed rank table output
by tlk (Acolyte) on Sep 08, 2019 at 07:27 UTC
    Wow! Thank you Knowledgeable Monks!
Re: Condensed rank table output
by tlk (Acolyte) on Sep 07, 2019 at 20:10 UTC
    Note to self: explore using reverse

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (2)
As of 2024-04-25 07:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found