Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

Aaaarghh...hashes of hashes..again :(

by viffer (Beadle)
on Mar 23, 2011 at 03:37 UTC ( #894925=perlquestion: print w/replies, xml ) Need Help??

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

Hi people of wisdom.
I'm REALLY struggling to get my head around the concept of hashes of hashes.
I've written a little script to enable members of my football team to vote for their player of the match on line. Then add up the total votes and allocate 3 points to the guy with the most votes, 2 to the 2nd most and 1 to the third most - and some less than nifty way of calculating when people have the same number of votes, but I digress...

I have ended up with 25 hashes, one per game. I KNOW I can make it one hash - and have worked out how to write the hashes 'on the fly'. The problem I'm now having, is trying to access the individual hashes within the overall hash. I was simply copying each one of the 25 hashes into another hash then calling the same subroutine each time. There HAS to be an easier way.

I originally had

my %G01totals = ( '01' => 0, '02' => 0, '03' => 0, '04' => 0, '05' => 0, '06' => 0, +'07' => 0, '08' => 0, '09' => 0, '10' => 0, '11' => 0, '12' => 0, '13' => 0, +'14' => 0, '15' => 0, '16' => 0, '17' => 0, '18' => 0, '19' => 0, '20' => 0, +'21' => 0, ); my %G02totals = ( '01' => 0, '02' => 0, '03' => 0, '04' => 0, '05' => 0, '06' => 0, +'07' => 0, '08' => 0, '09' => 0, '10' => 0, '11' => 0, '12' => 0, '13' => 0, +'14' => 0, '15' => 0, '16' => 0, '17' => 0, '18' => 0, '19' => 0, '20' => 0, +'21' => 0, ); my %G03totals = ( '01' => 0, '02' => 0, '03' => 0, '04' => 0, '05' => 0, '06' => 0, +'07' => 0, '08' => 0, '09' => 0, '10' => 0, '11' => 0, '12' => 0, '13' => 0, +'14' => 0, '15' => 0, '16' => 0, '17' => 0, '18' => 0, '19' => 0, '20' => 0, +'21' => 0, );
and updated the hashes via
while (my $line = <$file>) { chomp ($line); my ($in_game, $in_three, $in_two, $in_one) = split (',', $line) +; if ($in_game eq 'G01') { $G01totals{$in_three} += 3; $G01totals{$in_two} += 2; $G01totals{$in_one} += 1; } if ($in_game eq 'G02') { $G02totals{$in_three} += 3; $G02totals{$in_two} += 2; $G02totals{$in_one} += 1; } if ($in_game eq 'G03') { $G03totals{$in_three} += 3; $G03totals{$in_two} += 2; $G03totals{$in_one} += 1; } }
I've changed this to not having the hashes declared at all and populating them via
while (my $line = <$file>) { chomp ($line); my ($in_game, $three, $two, $one) = split (',', $line); $totals{$in_game}{$three} += 3; $totals{$in_game}{$two} += 2; $totals{$in_game}{$one} += 1; }
What I was previously doing (25 times) was
%totals = %G01totals; create_tables ($print_heading, $game_index, %totals);
I've changed this to
for (my $i=1;$i<3;$i++) { $print_heading = 1; $game_index = $i; create_tables ($print_heading, $game_index, %totals); }
But in my create_tables subroutine I'm really having problems trying to work out how to access the hash.

I did have

sub create_tables { my ($heading_flag, $game_no, %totals) = @_; if( %totals) { open( $out_file, '>', $obtain_321_file ) or die "G1 Open output file $obtain_321_file failed $!"; foreach $key (sort sortvalue (keys(%totals))) { if (! $totals{$key} == 0) { print ($out_file "$totals{$key},$players{$key}\n"); } } close ( $out_file ); calculate_overall_results(); close ( $out_file ); }
Which worked perfectly - when I knew what hash I was getting - since i was calling it for each hash. I've now tried using
sub create_tables { my ($heading_flag, $game_no, %totals) = @_; if( %totals) { open( $out_file, '>', $obtain_321_file ) or die "G1 Open output file $obtain_321_file failed $!"; foreach $game_no (sort keys %totals) { foreach my $key (sort keys %{$totals{$game_no}}) { if (! $totals{$game_no} == 0) { print ($out_file "$totals{$game_no}{$key},$players{$ +key}\n"); } } } close ( $out_file ); calculate_overall_results(); close ( $out_file ); }
and basically that's complete bollocks! I've lost count of the number of variations I've tried. The code in it's entirety can be found at

Working long version:
home.iprimus.com.au/viffer/white_vote_cur_standings.cgi.txt
Attempt to reduce hashes
home.iprimus.com.au/viffer/white_vote_results_new.cgi.txt

Abridged version of entire code
home.iprimus.com.au/viffer/voting_original.cgi.txt
Attempt to fix abridged version
home.iprimus.com.au/viffer/voting_updated.cgi.txt

If anyone could let me know how I can read each hash (in the right order) individually, I would be extremely grateful. Thanks Kev

Replies are listed 'Best First'.
Re: Aaaarghh...hashes of hashes..again :(
by GrandFather (Saint) on Mar 23, 2011 at 03:52 UTC

    Ok, let's go right back to the start. Where does your your input data come from and what does it look like? What I've seen so far suggests and array of games where each element contains an array of counts - no hashes at all!

    Not that is likely to fix your problem because you still have the same nested structure, but an array of arrays does seem at first glance to be a more natural fit to what I infer your data to be. The important thing about hashes and arrays is that they only store scalar values, but a reference is a scalar so you can store a reference to an array or hash in the element of an array or hash - hence you can have nested data structures.

    Note that it often helps to write a small test script that uses a little test data and prints a simple version of the results to figure out how to solve the tricky bit in isolation. Aside from making testing easier, that's also a good piece of code to use when you want input to solving the problem from others (like, oh I don't know, um Perl monks maybe).

    True laziness is hard work
      Cheers
      Data is entered via a web page and the votes get written to a file, looking like
      01,01,02,03,23/03/2011 12:27:41 01,03,05,08,23/03/2011 12:37:41 01,02,05,07,23/03/2011 12:47:41 01,03,02,03,23/03/2011 12:57:41 01,01,06,08,23/03/2011 12:67:41 02,01,02,03,24/03/2011 12:27:41 02,03,05,08,24/03/2011 12:37:41 02,02,05,07,24/03/2011 12:47:41 02,03,02,03,24/03/2011 12:57:41 02,01,06,08,24/03/2011 12:67:41
      where:
      the first value is the game, the second, third and fourth values are the players that have been voted for. The last is date and time.

      So in the above example, 5 votes have been made for both games one and two.

      In the first record
      01,01,02,03,23/03/2011 12:27:41
      For game 01, player 01 would get 3 points, player 02 would get 2 points and player 03 would get 1 point
      . I had tried to make a smaller version of the code (using only three tables rather than 25) but...still left me completely non plussed!

        If I understand what you want then the following may help:

        use strict; use warnings; my %totals; while (defined(my $line = <DATA>)) { chomp $line; my ($game, $first, $second, $third, $dates) = split /,/, $line; $totals{$game}{$first} += 3; $totals{$game}{$second} += 2; $totals{$game}{$third} += 1; } printResults($_, $totals{$_}) for sort keys %totals; sub printResults { my ($gameName, $gameTotals) = @_; my %byTotal; push @{$byTotal{$gameTotals->{$_}}}, $_ for keys %$gameTotals; my @scores = sort {$b <=> $a} keys %byTotal; my @place = qw{first second third}; @scores = grep {defined} @scores[0 .. 2]; print "Results for $gameName\n"; for my $score (@scores) { print "$place[0] with $score points: ", join(', ', @{$byTotal{ +$score}}), "\n"; shift @place; } print "\n"; } __DATA__ 01,01,02,03,23/03/2011 12:27:41 01,03,05,08,23/03/2011 12:37:41 01,02,05,07,23/03/2011 12:47:41 01,03,02,03,23/03/2011 12:57:41 01,01,06,08,23/03/2011 12:67:41 02,01,02,03,24/03/2011 12:27:41 02,08,05,08,24/03/2011 12:37:41 02,02,05,07,24/03/2011 12:47:41 02,03,02,03,24/03/2011 12:57:41 02,09,06,08,24/03/2011 12:67:41

        Prints:

        Results for 01 first with 8 points: 03 second with 7 points: 02 third with 6 points: 01 Results for 02 first with 7 points: 02 second with 5 points: 08, 03 third with 4 points: 05
        True laziness is hard work
Re: Aaaarghh...hashes of hashes..again :(
by NetWallah (Canon) on Mar 23, 2011 at 04:18 UTC
    Sounds to me like you will be far better off using a small database like sqlite.

    You need to separate your data from your business logic - data in tables like "Player", "Game" and "score", where "score" makes the relationship between a player and the game.

    This will allow simple to complex queries like "select name, gamescore from player inner join score on score.playerid=player.id order by gamescore descending limit 3" will give you the top 3 players across all games, unless you care to limit that.

    IMHO, this problem space is screaming for a database.

         Syntactic sugar causes cancer of the semicolon.        --Alan Perlis

      Normally I would agree, and then I would proceed to write a simple app based on DBIx::Class talking to a SQLite database.

      The problem is that databases are harder to understand than simple nested data structures, and if viffer is having trouble understanding a simple hash of hashes, then I think anything database related will be to hard.

      Instead I think we need to point the OP to some tutorials that better explain nested data structures, and answer any questions he may have. Once that is understood we can move onto something more advanced like a database.

        "databases are harder to understand than simple nested data structures"
        I would debate that (subject to the availability of cold beverages).

        In most cases I have encountered, relational databases offer a simpler, more directly-mapped representation of the problem space, when compared to multi-level data structures, especially when it comes to traversing said structures to get summaries. I find OOP is required to approach equivalent simplicity.

        I'm too lazy to substantiate that argument, so I'll just leave it there, and let you have the last word, should you so desire.

        Cheers!

             Syntactic sugar causes cancer of the semicolon.        --Alan Perlis

Re: Aaaarghh...hashes of hashes..again :(
by johngg (Canon) on Mar 23, 2011 at 10:03 UTC

    Not pertinent to your problem (and I note that you no longer initialise the hashes anyway) but you could save yourself some typing by using map and sprintf to do the work. Your

    my %G01totals = ( '01' => 0, '02' => 0, '03' => 0, '04' => 0, '05' => 0, '06' => 0, +'07' => 0, '08' => 0, '09' => 0, '10' => 0, '11' => 0, '12' => 0, '13' => 0, +'14' => 0, '15' => 0, '16' => 0, '17' => 0, '18' => 0, '19' => 0, '20' => 0, +'21' => 0, );

    could be written

    my %G01totals = map { sprintf( q{%02d}, $_ ) => 0 } 1 .. 21;

    I hope this might be useful in the future.

    Cheers,

    JohnGG

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://894925]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (3)
As of 2022-05-28 08:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Do you prefer to work remotely?



    Results (99 votes). Check out past polls.

    Notices?