Re: Sorting and ranking
by tybalt89 (Monsignor) on Jan 21, 2018 at 16:33 UTC
|
#!/usr/bin/perl
# http://perlmonks.org/?node_id=1207623
use strict;
use warnings;
my %data =
(
'car' => 180,
'motorcycle' => 150,
'skate' => 150,
'bird' => 120,
);
my ($n, @rank) = 1;
$rank[ $data{$_} ] .= "$_\n" for sort keys %data;
defined and $n += print s/^/$n - /gmr for reverse @rank;
Outputs:
1 - car
2 - motorcycle
2 - skate
3 - bird
| [reply] [d/l] [select] |
|
|
use strict;
use warnings;
use Data::Dumper;
my %data =
(
'car' => 10,
'motorcycle' => 7,
'skate' => 7,
'board' => 2,
'roller' => 4,
'vase' => 4,
'bird' => 4,
);
my ($n, @rank) = 1;
foreach( keys %data){
$rank[ $data{$_} ] .= "$_\n" ;
}
print Dumper(\@rank);
defined and $n += print s/^/$n - /gmr for reverse @rank;
__DATA__
$VAR1 = [
undef,
undef,
'board
',
undef,
'vase
bird
roller
',
undef,
undef,
'motorcycle
skate
',
undef,
undef,
'car
'
];
1 - car
2 - motorcycle
2 - skate
3 - vase
3 - bird
3 - roller
4 - board
| [reply] [d/l] |
|
|
You've discovered my secret. Oh, wait, it's not a secret, it's a distribution sort.
To see a hardware implementation of a distribution sort, check out IBM card sorter.
| [reply] |
|
|
$rank[ $data{$_} ] .= "$_\n" for sort keys %data;
defined and $n += print s/^/$n - /gmr for reverse @rank;
Clever as always :-) It should be noted, however, that memory usage (and speed) gets worse for increasingly large values in the hash, since it causes @rank to grow accordingly.
| [reply] [d/l] [select] |
|
|
Of course. One of the beauties of perl, however, is the ability to easily switch over to using a hash.
The sort will have to be explicit, of course. instead of getting it as a freebie with the array.
As a bonus, you get the ability to properly handle negative and/or non-integer values.
#!/usr/bin/perl
# http://perlmonks.org/?node_id=1207623
use strict;
use warnings;
my %data =
(
'car' => 180,
'motorcycle' => 150,
'skate' => 150,
'bird' => 120,
);
my ($n, %rank) = 1;
$rank{ $data{$_} + 0 } .= "$_\n" for sort keys %data;
$n += print s/^/$n - /gmr for @rank{ sort { $b <=> $a } keys %rank };
| [reply] [d/l] |
Re: Sorting and ranking
by haukex (Archbishop) on Jan 21, 2018 at 13:10 UTC
|
The code prints 1,2,3,4,
The code you've posted doesn't print that, it prints 'syntax error at 1207623.pl line 10, near "@sorted["'. Please post the actual code you are running. (How do I post a question effectively?) Also, always Use strict and warnings.
If I assume you meant my @sorted; @sorted[...] = ...;, then the code runs, but I think you're making things a little too complicated with the various arrays - note that the comparison $sorted[$count] != $sorted[$count-1] should always be true because you've assigned @sorted[...] = 1..@keys; that is, all the values of @sorted are different. Have a look at the Basic debugging checklist, especially the advice to use Data::Dumper or Data::Dump to look at your data structures.
Here's one way to approach this. I first sort the keys (see also How do I sort an array by (anything)?), and then just run through those keys and look at the current and previous value, increasing the rank if they differ. (This assumes no undef values in the hash, since I use that value to keep track if there was a $previous value or this is the first.)
use warnings;
use strict;
use Data::Dump;
my %data = (
'car' => 180,
'motorcycle' => 150,
'skate' => 150,
'bird' => 120,
);
my @keys = sort { $data{$b} <=> $data{$a} or $a cmp $b } keys %data;
my ($prev,$rank);
for my $k (@keys) {
$rank++ unless defined($prev) && $prev==$data{$k};
dd $k, $data{$k}, $rank;
$prev = $data{$k};
}
__END__
("car", 180, 1)
("motorcycle", 150, 2)
("skate", 150, 2)
("bird", 120, 3)
| [reply] [d/l] [select] |
|
|
Great! Thank you for your help!
| [reply] |
Re: Sorting and ranking
by 1nickt (Canon) on Jan 21, 2018 at 13:45 UTC
|
Hi, keep track of the ranking as you go through the sorted list, and only increment it if the value is lower.
Update: ++haukex posted basically the same solution while I was dithering over syntax, LOL!
use strict; use warnings; use feature 'say';
use Data::Dumper; $Data::Dumper::Sortkeys = $Data::Dumper::Terse = 1;
my %data = (
car => 180,
motorcycle => 150,
skate => 150,
bird => 120,
);
my @sorted = sort { $data{$b} <=> $data{$a} } keys %data;
my $rank;
my %result;
for ( 0 .. $#sorted ) {
my $curr_key = $sorted[$_];
my $prev_key = $_ ? $sorted[$_ - 1] : undef;
if ( not $prev_key ) {
$rank = 1;
} elsif ( $data{$curr_key} != $data{$prev_key} ) {
$rank++;
} else {
# values are the same, leave $rank unchanged
}
$result{$curr_key} = { val => $data{$curr_key}, rank => $rank };
}
say "$_: ", Dumper $result{$_} for @sorted;
__END__
Output:
perl 1207623.pl
car: {
'rank' => 1,
'val' => 180
}
skate: {
'rank' => 2,
'val' => 150
}
motorcycle: {
'rank' => 2,
'val' => 150
}
bird: {
'rank' => 3,
'val' => 120
}
Hope this helps!
The way forward always starts with a minimal test.
| [reply] [d/l] [select] |
Re: Sorting and ranking
by johngg (Canon) on Jan 21, 2018 at 13:50 UTC
|
Use a HoA keyed by the value and sort that in descending order, incrementing rank for each key. Uncomment the Data::Dumper lines to see the structure of the HoA. The code
use strict;
use warnings;
use 5.022;
# use Data::Dumper;
my %data = (
car => 180,
motorcycle => 150,
skate => 150,
bird => 120,
);
my %byValue;
push @{ $byValue{ $data{ $_ } } }, $_ for keys %data;
# print Data::Dumper->Dumpxs( [ \ %byValue ], [ qw{ *byValue } ] );
my $rank;
for my $descVal ( sort { $b <=> $a } keys %byValue )
{
$rank ++;
say qq{$rank - $_} for sort @{ $byValue{ $descVal } };
}
The output.
1 - car
2 - motorcycle
2 - skate
3 - bird
I hope this is helpful.
Update: An updated script to show the difference between "rank" and "place" as in "Fred and Mary were placed 3rd equal."
use strict;
use warnings;
use 5.022;
my %data = (
car => 180,
tortoise => 90,
concorde => 300,
motorcycle => 150,
bird => 120,
skate => 150,
pedestrian => 100,
rocket => 300,
aeroplane => 210,
unicycle => 175,
sheep => 100,
frog => 120,
cow => 110,
bus => 150,
);
my %byValue;
push @{ $byValue{ $data{ $_ } } }, $_ for keys %data;
say q{Rank Place Item};
my $rank = 0;
my $place = 1;
for my $descVal ( sort { $b <=> $a } keys %byValue )
{
$rank ++;
my $count = scalar @{ $byValue{ $descVal } };
my $eq = $count > 1 ? q{=} : q{ };
printf qq{%3d%5d%1s %s\n}, $rank, $place, $eq, $_
for sort @{ $byValue{ $descVal } };
$place += $count;
}
The output.
Rank Place Item
1 1= concorde
1 1= rocket
2 3 aeroplane
3 4 car
4 5 unicycle
5 6= bus
5 6= motorcycle
5 6= skate
6 9= bird
6 9= frog
7 11 cow
8 12= pedestrian
8 12= sheep
9 14 tortoise
| [reply] [d/l] [select] |
Re: Sorting and ranking
by Anonymous Monk on Jan 21, 2018 at 12:06 UTC
|
| [reply] |
|
|
But my sorting is actually based on the values, or?
sort { $data{$keys[$b]} <=> $data{$keys[$a]}
| [reply] [d/l] |
|
|
| [reply] |
|
|
Re: Sorting and ranking
by Anonymous Monk on Jan 24, 2018 at 09:36 UTC
|
use strict;
use warnings;
my %list=(
'car' => 180,
'motorcycle' => 150,
'skate' => 150,
'bird' => 120,
);
my @val = sort{$list{$a}<=>$list{$b}} keys %list;
my $teller = 0;
my $test = 0;
foreach my $value(@val){
if($test == $list{$value}){
print "$teller $value $list{$value}\n";
}
else{
$teller++;
print "$teller $value $list{$value}\n";
$test = $list{$value};
}
}
| [reply] [d/l] |