weird hash sort question

C

ccc31807

my @teams = ("Florida State","Ohio State","Auburn","Alabama" ...)
my %ratings;
#example values of %ratings
$ratings{BCS}{"Florida State"}{...}{...} = 'some value';
$ratings{BCS}{"Ohio State"}{...}{...} = 'some value';
$ratings{BCS}{"Auburn"}{...}{...} = 'some value';
$ratings{BCS}{"Alabama"}{...}{...} = 'some value';
$ratings{BCS}{...}{...}{...} = 'some value';
$ratings{...}{...}{...}{...} = 'some value';

foreach my $bowl (sort keys %ratings)
{
foreach my $team (sort keys %{$ratings->{$bowl}}
{
foreach my $somekey (sort keys %{$ratings->{$bowl}{etc}})
{
#doesn't matter
}
}
}

This sort routine gives values like:
BCS
Alabama
Auburn
Florida State
Ohio State
Harris
etc, etc, and etc.

WHAT I WANT IS THIS !!!!!
BCS
Florida State
Ohio State
Auburn
Alabama
Harris
etc, etc, and etc.

What I'd like to do (but doesn't work) is this:
foreach my $bowl (sort keys %ratings) # this is okay
foreach my $team (sort (@teams) keys %{$ratings->{$bowl})
{
#okay from here on out
}
}

Suggestions?

Thanks very, very much, CC.
 
B

Bjoern Hoehrmann

* ccc31807 wrote in comp.lang.perl.misc:
my @teams = ("Florida State","Ohio State","Auburn","Alabama" ...)

You can do something like this

my %team_sortkey = do {
my $i = 1;
map { $_ => $i++ } @teams;
};

That creates a hash like

"Florida State" => 1,
"Ohio State" => 2,
...

Then later sort by comparing the mapped values like

sort { $team_sortkey{$a} <=> $team_sortkey{$b} } @list_of_teams;

I believe there are CPAN modules that aid in sorting multi-dimensional
hashes as well as key-based sorting. For instance, my `List::OrderBy`
module would let you write

order_by { $team_sortkey{$_} } @list_of_teams;
 
B

Bjoern Hoehrmann

* Ben Morrow wrote in comp.lang.perl.misc:
Quoth (e-mail address removed):

Um, no, there's a *much* easier answer...

(Assuming, of course, that @teams is complete. If it isn't it gets a
little harder.)

Iterating over @teams does not sort the keys in the hash, so you are
more likely to end up with bugs later on without necessarily noticing
it at first, so I would not necessarily recommend doing that.
 
J

Jürgen Exner

Bjoern Hoehrmann said:
* Ben Morrow wrote in comp.lang.perl.misc:

Iterating over @teams does not sort the keys in the hash, so you are

You can never sort the keys in a hash. At most you can sort the keys of
a hash.
more likely to end up with bugs later on without necessarily noticing
it at first, so I would not necessarily recommend doing that.

And the usual way to sort by a custom criteria is to define a custom
function that returns -1, 0, or 1 depending upon if the left argument is
smaller, the same, or a larger than the right argument (aka $a and $b).

In this case here it appears the OP wants to sort his data by the
sequence defined in @teams. If so (guessing here, he didn't say so
explicitely) then we need a custom compare function for those elements.
The OP can either define that from scratch or leverage the existing
sequence in @teams using e.g. firstidx() from List::MoreUtils or use one
of the many other approaches described at
http://www.perlmonks.org/?node_id=75660.

While this may not be very efficient it is a well-designed and clean
approach. If you are looking for a more efficient way then I think
applying a Schwartzian Transformation may be called for.

jue
 
R

Rainer Weikusat

Ben Morrow said:
Quoth ccc31807 said:
my @teams = ("Florida State","Ohio State","Auburn","Alabama" ...) [...]

foreach my $bowl (sort keys %ratings)
{
foreach my $team (sort keys %{$ratings->{$bowl}} [...]

This sort routine gives values like:
BCS
Alabama
Auburn
Florida State
Ohio State
Harris
etc, etc, and etc.

WHAT I WANT IS THIS !!!!!
BCS
Florida State
Ohio State
Auburn
Alabama
Harris
etc, etc, and etc.

What I'd like to do (but doesn't work) is this:
foreach my $bowl (sort keys %ratings) # this is okay
foreach my $team (sort (@teams) keys %{$ratings->{$bowl})
{

Stop and think about what you're doing for a minute, instead of just
retyping code you've written before. What does

keys %{$ratings->{$bowl}}

return?

A set of unknown strings, probably containing 'Florida State', 'Ohio
State', 'Auburn', 'Alabama' and 'etc, etc. and etc.'.
What does sort do to that list?

It probably reorders it somehow.
What will $team be set to each time round the loop?

To some string.
Now, how does the list you have in @teams compare with any of those
things?

It contains strings as well.

The only sensible question here is "What are you trying to do?" and the
answer must not be pseudo-Perl not even the author considers to be
adequate to describe that.
 
C

ccc31807

A set of unknown strings, probably containing 'Florida State', 'Ohio
State', 'Auburn', 'Alabama' and 'etc, etc. and etc.'.
It probably reorders it somehow.
It contains strings as well.
The only sensible question here is "What are you trying to do?" and the
answer must not be pseudo-Perl not even the author considers to be
adequate to describe that.

SOLVED!!!!

And yeah, I'm kind of excited. I spent most of the afternoon on this instead of working, and got frustrated. My test program and output follows. Comments and criticisms humbly accepted.
#-------------------sorthash.plx-------------------
#! perl
use strict;
use warnings;
use List::MoreUtils qw ( indexes );
use v5.10;

say qq(sorting hash values.........);
my @campi = qw( T01 M01 D01 ETROY GC T02);
foreach (@campi) { print " $_ "; }
print "\n";

my %h = (
M01 => 44,
T01 => 456,
ETROY => 342,
GC => 498,
T02 => 9,
D01 => 108,
);

foreach my $k (sort keys %h) { say "$k => $h{$k}"; }
print "-----------------------------\n";
foreach my $k (sort { $h{$a} <=> $h{$b} } keys %h) { say "$k => $h{$k}"; }
print "-----------------------------\n";
foreach my $k (sort {
my $x = indexes {$_ eq $a} @campi;
my $y = indexes {$_ eq $b} @campi;
#print "a $a b $b x $x y $y\n";
$x > $y ? 1 : -1 ;
} keys %h) { say "$k => $h{$k}"; }

exit(0);
#----------------output--------------
perl sorthash.plx
sorting hash values.........
T01 M01 D01 ETROY GC T02
D01 => 108
ETROY => 342
GC => 498
M01 => 44
T01 => 456
T02 => 9
-----------------------------
T02 => 9
M01 => 44
D01 => 108
ETROY => 342
T01 => 456
GC => 498
-----------------------------
T01 => 456
M01 => 44
D01 => 108
ETROY => 342
GC => 498
T02 => 9
 
C

Charlton Wilbur

RW> It contains strings as well.

It contains the keys of the hash, in the order the OP wants them to
appear in for display.

Charlton
 
R

Rainer Weikusat

[...]
SOLVED!!!!

And yeah, I'm kind of excited. I spent most of the afternoon on this instead of working, and got frustrated. My test program and output follows. Comments and criticisms humbly accepted.
#-------------------sorthash.plx-------------------
#! perl
use strict;
use warnings;
use List::MoreUtils qw ( indexes );
use v5.10;

say qq(sorting hash values.........);
my @campi = qw( T01 M01 D01 ETROY GC T02);
foreach (@campi) { print " $_ "; }
print "\n";

my %h = (
M01 => 44,
T01 => 456,
ETROY => 342,
GC => 498,
T02 => 9,
D01 => 108,
);

foreach my $k (sort keys %h) { say "$k => $h{$k}"; }
print "-----------------------------\n";
foreach my $k (sort { $h{$a} <=> $h{$b} } keys %h) { say "$k => $h{$k}"; }
print "-----------------------------\n";
foreach my $k (sort {
my $x = indexes {$_ eq $a} @campi;
my $y = indexes {$_ eq $b} @campi;
#print "a $a b $b x $x y $y\n";
$x > $y ? 1 : -1 ;
} keys %h) { say "$k => $h{$k}"; }

This is pretty insane coding because it does two linear searches of the
@campi array for each comparison (To a degree. For a small @campi, this
might be faster than doing two hash lookups per comparison or at least,
in can be implemented such that it is. But it's nevertheless quadratic).

It is possible to build a suitable map/ hash with map like this:

my %campi = map { $campi[$_], $_ } 0 .. $#campi;

and then use

foreach my $k (sort { $campi{$a} <=> $campi{$b} } keys %h) { say "$k => $h{$k}"; }

Or just use

for (@campi) { say "$_ => $h{$_}"; }

NB: Both approaches were suggested already.

..
 
G

George Mpouras

Στις 4/12/2013 00:27, ο/η ccc31807 έγÏαψε:
What I'd like to do (but doesn't work) is this:
foreach my $bowl (sort keys %ratings) # this is okay
foreach my $team (sort (@teams) keys %{$ratings->{$bowl})
{
#okay from here on out
}
}

Suggestions?

Thanks very, very much, CC.


# this is what you want



#!/usr/bin/perl
use strict;
use warnings;

my @teams = ("Florida State","Ohio State","Auburn","Alabama");
my %ratings;


$ratings{BCS}->{"Ohio State"} =1;
$ratings{BCS}->{"Florida State"} =1;
$ratings{BCS}->{"Alabama"} =1;
$ratings{BCS}->{"Auburn"} =1;


foreach my $bowl (sort{ SoA($a,\@teams) <=> SoA($b,\@teams) } keys
%{$ratings{BCS}})
{
print "$bowl\n";
}




sub SoA
{
for (my $i=0; $i < @{$_[1]}; $i++)
{
return $i if $_[1]->[$i] eq $_[0]
}
}
 
R

Rainer Weikusat

[...]
sub SoA
{
for (my $i=0; $i < @{$_[1]}; $i++)
{
return $i if $_[1]->[$i] eq $_[0]
}
}

Perl already knows how to count, consequently, it is not necessary
to tell it how to do that:

for (0 .. $#{$_[1]}) {
return $_ if $_[1]->[$_] eq $_[0];
}

or the more compact

sub ndx {
$_[1]->[$_] eq $_[0] and return $_ for 0 .. $#{$_[1]};
}

This can also be written as

use List::Util qw(first);

sub ndx {
return first { $_[1]->[$_] eq $_[0]} 0 .. $#{$_[1]};
}

Although that's IMO not really preferable.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top