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
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.
| [reply] [d/l] |
|
Good shout, thx.
PS guidance is OK :)
| [reply] |
|
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
| [reply] [d/l] [select] |
|
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,
'Ĵ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)
| [reply] [d/l] [select] |
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
| [reply] [d/l] [select] |
|
Cheers for a true Perl-y way))
| [reply] |
|
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.
| [reply] |
|
Re: Condensed rank table output
by jcb (Parson) on Sep 07, 2019 at 21:12 UTC
|
# 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. | [reply] [d/l] |
|
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 ;)
| [reply] [d/l] [select] |
|
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.
| [reply] [d/l] |
|
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.
| [reply] |
Re: Condensed rank table output
by Lotus1 (Vicar) on Sep 07, 2019 at 22:38 UTC
|
| [reply] |
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"
| [reply] [d/l] |
Re: Condensed rank table output
by tlk (Acolyte) on Sep 08, 2019 at 07:27 UTC
|
Wow! Thank you Knowledgeable Monks! | [reply] |
Re: Condensed rank table output
by tlk (Acolyte) on Sep 07, 2019 at 20:10 UTC
|
Note to self: explore using reverse | [reply] |
|
|