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

Dear Monks,

I am working with hash of arrays that need to be sorted. I would like the order to be driven by the highest Score value per entry key and then sorted within each key based on Score. It would be easy if not for the Location arguments that I would like to mirror the Score sorting. Example;

my %entry; push @{$entry{'BLA'}{Score}}, 5; push @{$entry{'BLA'}{Location}}, '1-10'; push @{$entry{'TRA'}{Score}}, 15; push @{$entry{'TRA'}{Location}}, '7-15'; push @{$entry{'TRA'}{Score}}, 23; push @{$entry{'TRA'}{Location}}, '4-19'; push @{$entry{'TRA'}{Score}}, 2; push @{$entry{'TRA'}{Location}}, '78-120'; push @{$entry{'BLA'}{Score}}, 10; push @{$entry{'BLA'}{Location}}, '2-10'; Output; TRA 23 4-19 TRA 15 7-15 TRA 2 78-120 BLA 10 2-10 BLA 5 1-10

Any ideas? Speed is a factor and for various reasons within the code I have to stick with this particular data structure. I presume once it's sorted and I don't add anything it will stay sorted? Thanks for help.

Replies are listed 'Best First'.
Re: Advance Sorting
by tangent (Parson) on Jun 03, 2014 at 17:24 UTC
    I take it you want to sort first by the key which contains the highest score, and then within each key by that key's score. Here's one way to do it:

    Update: removed unnecessary intermediate array

    my @sorted; for my $key (keys %entry) { my $scores = $entry{$key}{'Score'}; my $locations = $entry{$key}{'Location'}; my @array = map { [ $key, $scores->[$_],$locations->[$_] ] } 0 .. +$#$scores; @array = sort { $b->[1] <=> $a->[1] } @array; push(@sorted, [ $array[0][1], \@array ]); } @sorted = sort { $b->[0] <=> $a->[0] } @sorted; for my $array (@sorted) { my $aoa = $array->[1]; for my $result (@$aoa) { print "$result->[0] $result->[1] $result->[2]\n"; } }
    Output:
    TRA 23 4-19 TRA 15 7-15 TRA 2 78-120 BLA 10 2-10 BLA 5 1-10
      Brilliant! Exactly what I wanted. Thanks a lot.
Re: Advance Sorting
by LanX (Saint) on Jun 03, 2014 at 16:35 UTC
    > I presume once it's sorted and I don't add anything it will stay sorted?

    Nope. Hashes can't be sorted, only their keys can be sorted and held in a separate array.

    Otherwise please elaborate better what you want to achieve your post is rather cryptic...

    Cheers Rolf

    (addicted to the Perl Programming Language)

      I see thanks for that. I have a list of IDs with a corresponding score and location values. I want to order the IDs based on highest score associated with them. I also want the IDs to be grouped together. When I use array of hashes like below how would I order it then?

      push @entry, { ID => 'BLA', Score => 5, Location => '1-10', }; push @entry, { ID => 'TRA', Score => 15, Location => '7-15', }; push @entry, { ID => 'BLA', Score => 10, Location => '2-10', }; Output; TRA 15 7-15 BLA 10 2-10 BLA 5, 1-10
        That's a completely different data structure now!!!

         @sorted = sort { $a->{Score} <=> $b->{Score} } @entry

        Does it.

        Cheers Rolf

        (addicted to the Perl Programming Language)

Re: Advance Sorting
by poj (Abbot) on Jun 03, 2014 at 19:40 UTC
    A database solution for you to consider ;
    #!perl use strict; use warnings; use DBI; # set up my %entry=(); while (<DATA>){ my ($id,$score,$locn) = split '\s+',$_; push @{$entry{$id}{Score}} ,$score; push @{$entry{$id}{Location}},$locn; } # create db my $dbfile = 'scores.sqlite'; unlink($dbfile); my $dbh = DBI->connect('dbi:SQLite:dbname='.$dbfile , undef , undef , {RaiseError =>1, AutoCommit =>1}) or die $DBI::errstr; $dbh->do('CREATE TABLE SCORES (ID,SCORE integer,LOCATION)'); $dbh->do('CREATE TABLE LEAGUE (ID, HI_SCORE integer)'); # load data my $sth = $dbh->prepare('INSERT INTO SCORES VALUES (?,?,?)'); for my $id (keys %entry){ my $rec = $entry{$id}; my @score = @{$rec->{'Score'}}; my @locn = @{$rec->{'Location'}}; for my $i (0..$#score){ $sth->execute($id,$score[$i],$locn[$i]); } } # aggregate $dbh->do('INSERT INTO LEAGUE (ID, HI_SCORE) SELECT ID, MAX(SCORE) FROM SCORES GROUP BY ID'); # report my $sql = 'SELECT S.ID, SCORE, LOCATION FROM SCORES AS S LEFT JOIN LEAGUE AS L ON S.ID = L.ID ORDER BY HI_SCORE DESC, SCORE DESC'; my $ar = $dbh->selectall_arrayref($sql); for (@$ar){ printf "%-5s %5d %10s\n",@$_,"\n"; } $dbh->disconnect; __DATA__ TRA 15 7-15 TRA 23 4-19 TRA 2 78-120 BLA 5 1-10 BLA 10 2-10
    poj
      Interesting idea, will give it a try. Thanks for that
Re: Advance Sorting
by AnomalousMonk (Archbishop) on Jun 04, 2014 at 00:05 UTC

    Here's a decorate-sort-undecorate (GRT) variation that may be useful if speed is an issue. Speaking of issues, there is a corner-case not handled here in which a  Score array (and its corresponding  Location array) is empty. If you're creating entries by push-ing to a hash as shown in the OP, I don't think this situation can arise, but I just thought I'd mention it.

      Will have a look how much faster it is. Thanks for the input.