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

Problem background is that we have a file that contains logs of data for error codes, and my boss now wants this to be tallied up into something that is useable for analysis. There are 3 basic pieces of information that need to be tallied :

Country
Error code
Message Type

The requirement is to be able to produce a report at anytime from this file to show the number of errors per Message type per error code per country, as well as the number of errors per Message type per country per error code.

My immediate thought was to do a 3 dimensional hash as it would be indexed and therefore should be easier to manipulate and handle. However, I am really struggling to determine how to define the hash below the 2nd level.

For example for the first scenario:
%hash=("$country"=>{ "$errorcode=>{ $messagetype=>{ "count"=>"value"}}})
Then to check for existence of a key it would be :
if (defined $hash{$country}{$errorcode}{$message}{$count}) { $hash{$country}{$errorcode}{$message}{$count}+=1; } else { $hash{$country}{$errorcode}{$message}{$count}=1; }
Then we get onto the problem of printing the darn thing.
for $varctry(keys %hash) { for $varerror(@$hash{$varctry}) { for $varmsgtype(@$hash{$varctry}{$varerror}) { print "Country $varctry with error $varerror for msg $varmsgtype h +as $hash{$varctry}{$varerror}{$varmsgtype}{count} errors\n"; } } }
I have tried various combinations, but a lot of the time I am getting warnings back saying that I do not have even amounts of values in my hash, meaning that I don’t have a straight key to value relationship, so I must be missing something obvious here! Any tips you have for me would be greatly appreciated.
Thank you in advance.

Replies are listed 'Best First'.
Re: 3 dimensional hashes!
by blue_cowdawg (Monsignor) on Apr 27, 2004 at 11:18 UTC

    You're almost there.

    use strict; : : Somewhere there is a loop feeding this : and everything gets defined as appropriate. if ( $tally{$country}->{$error}->{$type} ) { $tally{$country}->{$error}->{$type}++; } else { $tally{$country}->{$error}->{$type} = 1; } : : rest of loop : # print our tallies: foreach my $ctry(keys %tally){ foreach my $error(keys %{$tally{$ctry}} { foreach my $type (keys %{$tally{$ctry}->{$error}){ printf "%s,%s,%s = %d\n",$ctry,$error,$type, $tally{$ctry}->{$error}->{$type}; } } }

    disclaimer: Haven't had my coffee yet, and this is totally untested.

    Hope this helps!

      missed a couple of closing } but once those were in worked a treat! many thanks for your help.
Re: 3 dimensional hashes!
by TomDLux (Vicar) on Apr 27, 2004 at 14:39 UTC
    %hash=("$country"=>{ "$errorcode=>{ $messagetype=>{ "count"=>"value"}}})

    is a four dimensional hash, not three dimensionial.

    You also sometimes use count and sometimes $count. In fact, the way you use it when you're trying to print out the values, I get the impression that $hash{$country}{$errorcode}{$message} only has one key, 'count'. Since %hash is such an uninformative name, why not save a dimension and move 'count' to be the name:

    $count{$country}{$errorcode}{$message} += 1;

    --
    TTTATCGGTCGTTATATAGATGTTTGCA

Re: 3 dimensional hashes!
by Fletch (Bishop) on Apr 27, 2004 at 16:41 UTC

    This is the type thing that an RDBMS excells at. Just throw the data into one (even DBD::SQLite would probably suffice) and then select the counts using GROUP BY apropriately.

Re: 3 dimensional hashes!
by TomDLux (Vicar) on Apr 27, 2004 at 14:21 UTC

    If that country/code/message/count combination has already been assigned to, you want to increment the value; otherwise you want to initialize the value to one.

    If the value does not already exist, using it will automatically initialize it with the value undef, which in numerical contexts counts as zero. Incrementing undef, i.e. incrementing zero, produces one. So you can collapse your eight lines to:

    $hash{$country}{$errorcode}{$message}{$count}+=1;

    --
    TTTATCGGTCGTTATATAGATGTTTGCA