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

A function call is returning a struct that Data::Dumper says looks like this (I've elided some entries):

{ + '771271' => bless( { + 'Language' => 'en', + 'Rating' => '10.0000', + 'BannerType2' => 'seasonwide', + 'Season' => '1', + 'BannerPath' => 'seasonswide/79349-1-3. +jpg', 'BannerType' => 'season', + 'RatingCount' => '1' + }, 'DBM::Deep::Hash' ), + '586831' => bless( { + 'Language' => 'en', + 'Rating' => '5.5000', + 'BannerType2' => 'seasonwide', + 'Season' => '4', + 'BannerPath' => 'seasonswide/79349-4-3. +jpg', 'BannerType' => 'season', + 'RatingCount' => '2' + }, 'DBM::Deep::Hash' ), + '771301' => bless( { 'Language' => 'en', 'Rating' => '10.0000', 'BannerType2' => 'seasonwide', 'Season' => '4', 'BannerPath' => 'seasonswide/79349-4.jp +g', 'BannerType' => 'season', 'RatingCount' => '1' }, 'DBM::Deep::Hash' ) };
I want to iterate over the entries and extract the BannerPath with the highest Rating for a given Season. What's the best approach? My solution feels a bit clunky...
my $hdata = <call that creates the struct>; my ($rating, $bwurl); my @keys = keys %$hdata; foreach my $id (@keys) { my $hr2 = $hdata->{$id}; if (($hr2->{Season} == $seasonnum) && ($hr2->{Rating} > $rating)) +{ $rating = $hr2->{Rating}; $bwurl = $hr2->{BannerPath}; } }

Replies are listed 'Best First'.
Re: Iterating hash of blessed hashes
by BrowserUk (Patriarch) on Feb 17, 2011 at 05:00 UTC
    What's the best approach? My solution feels a bit clunky...

    You could omit a couple of temporary variables and initialising $rating wouldn't go amiss:

    my $hdata = <call that creates the struct>; my ($rating, $bwurl) = 0; foreach my $id ( keys %$hdata ) { if($hdata->{$id}{Season} == $seasonnum && $hdata->{$id}{Rating} > +$rating){ $rating = $hdata->{$id}{Rating}; $bwurl = $hdata->{$id}{BannerPath}; } }

    But essentially what you have is about as good as it gets.

    You might use List::Util::reduce(), but it is certainly no clearer:

    use List::Util qw[ reduce ]; ... my $hdata = <call that creates the struct>; my( $rating, $bwurl ) = @{ reduce{ if( $hdata->{$b}{Season} == $seasonnum && $hdata->{$b}{Rating} > $ +a->[0] ){ $a->[0] = $hdata->{$b}{Rating}; $a->[1] = $hdata->{$b}{BannerPath} } } [ 0, '' ], keys %hdata };

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Iterating hash of blessed hashes
by ikegami (Patriarch) on Feb 17, 2011 at 06:38 UTC

    In my opinion, a better use of reduce:

    use List::Util qw( reduce ); my $highest_rated = reduce { $a->{Rating} >= $b->{Rating} ? $a : $b } grep $_->{Season} == $seasonnum, values %$hdata; my $banner_path = $highest_rated->{BannerPath};

    Using procedural style instead,

    my $highest_rated; for (values(%$hdata)) { next if $_->{Season} != $seasonnum; $highest_rated //= $_; if ($_->{Rating} > $highest_rated->{Rating}) { $highest_rated = $_; } } my $banner_path = $highest_rated->{BannerPath};

      ++ your use of values. But you missed (your own) trick with grep in your procedural version:

      my ($rating, $bwurl) = 0; foreach my $subhash ( grep $_->{Season} == $seasonnum, values %$hdata +) { if( $subhash->{Rating} > $rating){ $rating = $subhash->{Rating}; $bwurl = $subhash->{BannerPath}; } }

      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        I didn't miss, I was avoiding functional-style functions including grep. But by all means, mix and match to your liking.
Re: Iterating hash of blessed hashes
by AnomalousMonk (Archbishop) on Feb 17, 2011 at 08:29 UTC

    ron7: As BrowserUk suggests, your OPed code has a problem on the first pass through the loop because you are comparing against $rating, which is uninitialized on that occasion. (I'm not sure how you missed this warning. You did use warnings and strictures, didn't you?) However, initialization to zero will be a problem if a rating can ever be negative (a point on which your OP is silent) and all the ratings happen to be so, in which case you will get a bunch more warnings and also a wrong answer!

      Didn't see any warning, but the code quickly evolved to this:
      my $hr = $tvdb->getSeriesBanners($title, 'season', 'seasonwide'); my $rating = 0; my $bwurl; my @keys = keys %$hr; foreach my $id (@keys) { my $hr2 = $hr->{$id}; if (($hr2->{Season} == $seasonnum) && ($hr2->{Rating} > $rating)) +{ $rating = $hr2->{Rating}; $bwurl = $hr2->{BannerPath}; } } if ($bwurl) { debug(1, "Using highest rated Series Banner\n"); return "http://thetvdb.com/banners/_cache/$bwurl"; }

      In which I now see a bug: If the only banner(s) available have a zero Rating, I end up with nothing. I'll change it to -1.

      The Rating is a 0..10 scale AFAIK (Thetvdb.com via TVDB::API), and I always use strict; use warnings;

Re: Iterating hash of blessed hashes
by Anonymous Monk on Feb 17, 2011 at 05:20 UTC
    My solution feels a bit clunky...

    At first I was going to suggest Data::DPath (meh) or (better) Class::XPath for slickness, but seeing how you're using DBM, just use DBI/DBD::DBM, something like

    my $dbh = DBI->connect('dbi:DBM:', ... my $sql = " SELECT Rating, BannerPath FROM table WHERE Season = ? AND Rating > ? "; my $sth = $dbh->prepare($sql); $sth->execute( 'Season', 6.0 );; $sth->dump_results if $sth->{NUM_OF_FIELDS}; $dbh->disconnect;

    See also DBI, Tutorials: Database Programming, http://w3schools.com/sql/

    Also , data goes in <c></c> tags

      Hadn't thought of that, but I'm actually using DBM::Deep so the SQL trick would probably not work.
        so the SQL trick would probably not work.

        Why? Even if DBD::DBM doesn't support DBM::Deep , DBM::Deep still has DBI storage -- slick :)

Re: Iterating hash of blessed hashes
by Anonymous Monk on Feb 17, 2011 at 04:50 UTC