in reply to Sorting values of nested hash refs

Is this what you want?
#!/usr/bin/perl use strict; use warnings; use Data::Dumper; my $hoh = { '3094507' => { '2838413' => '22', '2976790' => '33', '539960' => '10', '2152725' => '19', '161546' => '5', '2197572' => '3' }, '2946464' => { '2952428' => '9', '3913125' => '10', '790014' => '2', '1065575' => '3' }, '2565888' => { '10538' => '1', '744' => '2' }, }; my @results; foreach my $outer (keys %$hoh) { foreach my $inner ($hoh->{$outer}) { my $max = 0; for (values %$inner) { $max = $_ if ($_ > $max); } push @results, $max; } } print Dumper(@results);
Addon: If you use List::Util you can make it a little bit easier:
my $max = max (values %$inner);
best regards, neniro

Replies are listed 'Best First'.
Re: Re: Sorting values of nested hash refs
by Limbic~Region (Chancellor) on Feb 29, 2004 at 13:25 UTC
    neniro,
    I do not believe this accomplishes what doran was after. Instead of finding the 3 highest values of the inner hashes, you are finding the highest value in each inner hash. I think the way to go is to sort all the second level hash values into an array and pick the top 3.
    my @vals = sort {$b <=> $a } map { values %{$hoh->{$_}} } keys %$hoh; my ($one, $two, $three) = @vals[0..2];
    Cheers - L~R

    Update Completely missed that tinita had an almost identical solution below. In the case of a very large HoH, you can avoid sorting and gain a bit of efficiency with the following:

    my @top_3; my $filled = 0; VAL: for my $val ( map { values %{$hoh->{$_}} } keys %$hoh ) { if ( ! $filled ) { for ( 0 .. 2 ) { if ( ! defined $top_3[$_] ) { $top_3[$_] = $val; $filled = 1 if $_ == 2; next VAL; } } } if ( $val > $top_3[0] ) { ($top_3[0], $top_3[1]) = ($val, $top_3[0]); } elsif ( $val > $top_3[1] ) { ($top_3[1], $top_3[2]) = ($val, $top_3[1]); } elsif ( $val > $top_3[2] ) { $top_3[2] = $val; } } print "$_\n" for @top_3;
      Well (d'oh), an important thing I forgot to mention is that I also need to know where I am in the $HoH for any given value. So I not only need to find the top x (10 in my case) values of $HoH->{$foo}{$bar}, but I also need to keep track of the value of $foo and $bar for each of those x values.

      Anyway, your code gave me some idea and this is what I've come up with for now.
      while (my $kid_ = $kills_sth->fetchrow_hashref()){ $KP->{$kid_->{k}}{$kid_->{v}}++; } # How many do we keep? my $total = 10; my %top; for(keys %{$KP}){ # Player ID my $pid = $_; VAL: for ( keys %{$KP->{$pid}}) { # Target ID my $tid = $_; my $frags = $KP->{$pid}{$tid}; for(0 .. ($total - 1)){ my $idx = $_; $top{$idx}{f} ||= 0; if(($idx != ($total - 1)) && $frags > $top{$idx}{f}){ my $idx2 = $idx + 1; $top{$idx2}{k} = $top{$idx}{k}; $top{$idx2}{v} = $top{$idx}{v}; $top{$idx2}{f} = $top{$idx}{f}; $top{$idx}{k} = $pid; $top{$idx}{v} = $tid; $top{$idx}{f} = $frags; next VAL; } elsif(($idx == ($total - 1)) && ($frags > $top{$idx}{f})){ $top{$idx}{k} = $pid; $top{$idx}{v} = $tid; $top{$idx}{f} = $frags; next VAL; } } } } for(0 .. ($total - 1)){ my $id = $_; printf("%2s. p: %s\tt: %s\tf: %s\n",($id+1),$top{$id}{k},$top{$id} +{v},$top{$id}{f}); }
      Still, it seems awfully inefficient to me and I'm really leary of it's ninja scaling abilities. But that said, it works and produces the intended results:
      1. p: 2838413 t: 3440380 f: 328 2. p: 2838413 t: 1261188 f: 282 3. p: 539960 t: 2976790 f: 273 4. p: 3440380 t: 2838413 f: 268 5. p: 182560 t: 53957 f: 206 6. p: 539960 t: 53957 f: 196 7. p: 1261188 t: 53957 f: 190 8. p: 539960 t: 3440380 f: 171 9. p: 3440380 t: 539960 f: 146 10. p: 53957 t: 1261188 f: 127

      I'd appreciate any ideas about increasing efficiency.

      Thanks a bunch for your help.

      ps. Yes, it's a half-life stats program. :)
        doran,
        I think you might find the following a bit more efficient and a bit more readable:
        # An inline loop is more efficient then a block - thought almost imper +ceptable $KP->{ $_->{k} }{ $_->{v} }++ while $kills_sth->fetchrow_hashref(); # Before I did not know if it was safe to assume no negative numbers - + now I can my %top = map { $_ => {'score' => 0, 'pid' => '', 'tid' => ''} } 1 .. +10; my @items = qw(score pid tid); for my $pid ( keys %{ $KP } ) { SCORE: for my $tid ( keys %{ $KP->{$pid} } ) { my $score = $KP->{$pid}{$tid}; next if $score < $top{10}{score}; # May or may not improve +efficiency for my $rank ( 1 .. 10 ) { if ( $score > $top{$rank}{score} ) { (@{$top{$rank}}{@items}, @{$top{$rank + 1}}{@items}) = + ($score, $pid, $tid, @{$top{$rank}}{@items}); next SCORE; } } } } for my $rank ( 1 .. 10 ) { printf("%.2d. f: %s\tp: %s\tt: %s\n", $rank, @top{@items}); }
        This would be much more efficient if it could be done with SQL. You would have to provide more info to determine that.

        Cheers - L~R

        Update: 2004-03-17 This is broken. To fix it you would make %top an AoA, handle fence post issues, and splice entries in the middle while popping off the end. This version will randomly lose values. See this for a working example.

Re: Re: Sorting values of nested hash refs
by doran (Deacon) on Mar 01, 2004 at 06:31 UTC
    Thanks, but as Limbic~Region says, this isn't quite what I'm looking for. It may end up being that a 'first level' hash ref may contain more than one of our final set of values.