3

Updated:

After my initial post and responses, I managed to have another crack and have written out my aims and results a bit clearer:

Aim:

I'm attempting to count the number of hits in a search string of a log file to figure out how many occurrences of a message are generated in the following ways:

  • Total per day.
  • Total per hour.
  • Highest per min, per hour.
  • Highest per sec, per hour.

My working code:

#!/usr/bin/perl
#use strict;
use warnings;
use Data::Dumper;

my @a =  (  
    [ qw /2012-02-21_09:43:43/ ],
    [ qw /2012-02-21_09:43:43/ ],
    [ qw /2012-02-21_09:43:44/ ],
    [ qw /2012-02-21_09:43:44/ ],
    [ qw /2012-02-21_09:43:44/ ],
    [ qw /2012-02-21_09:43:45/ ],
    [ qw /2012-02-21_09:43:45/ ],
    [ qw /2012-02-21_09:43:45/ ],
    [ qw /2012-02-21_09:43:45/ ],
    [ qw /2012-02-21_09:44:47/ ],
    [ qw /2012-02-21_09:44:47/ ],
    [ qw /2012-02-22_09:44:49/ ],
    [ qw /2012-02-21_10:44:49/ ]
);

my ( %count, $count ) = ();

foreach (@a) {
    my $line = @$_[0] ;
    $line =~ /(\S+)_(\d+):(\d+):(\d+)/ ;

    my $day = $1;
    my $hour= $2;
    my $min = $3;
    my $sec = $4;

    $count {$day}->{$hour}->{$min}->{$sec}{'sec'} += 1 ;
    $count {$day}->{$hour}->{$min}{'min'} += 1 ;
    $count {$day}->{$hour}{'hour'} += 1 ;
    $count {$day}{'day'}  += 1 ;
}

#print Dumper (%count) . "\n";

foreach my $k1 ( sort keys %count ) {
    print "$k1\t$count{$k1}{'day'}\n" ;

    foreach my $k2 ( sort keys %{$count{$k1}} ) {
        if ($k2 =~ /day/) {
            next;
        }
        print " $k2:00\t\t$count{$k1}{$k2}->{'hour'}\n";

        foreach my $k3 ( sort keys %{$count{$k1}{$k2}} ) {
            if ($k3 =~ /hour/) {
                next;
            }
            print "  $k2:$k3\t\t$count{$k1}{$k2}{$k3}->{'min'}\n";

            foreach my $k4 ( sort keys %{$count{$k1}{$k2}{$k3}} ) {
                if ($k4 =~ /min/) {
                    next;
                }
                print "   $k2:$k3:$k4\t$count{$k1}{$k2}{$k3}{$k4}->{'sec'}\n";              
            }
            print "\n";
        }
        print "\n";
    }
}
exit;

Results

I've had to turn off strict (of which I am ashamed), due to my poor hash dereference methods.

2012-02-21  12
 09:00      11
  09:43     9
   09:43:43 2
   09:43:44 3
   09:43:45 4

  09:44     2
   09:44:47 2

 10:00      1
  10:44     1
   10:44:49 1

Attempting to output:

2012-02-21  12
 09:00      11
  09:43     9
   09:43:45 4   

 10:00      1
  10:44     1
   10:44:49 1

Questions:

  1. Is there a better way of writing the code, and turning on strict?
  2. How would I go about listing the highest occurrence of a hash value within a hash, in an attempt to list the highest number count only?

Thanks for all the previous posts, I couldn't have gotten this far without them.

Cheers,

Andy

user1039417
  • 109
  • 9
  • Turning off strict to "solve" problems is like Homer Simpson putting a piece of tape over the "Low oil" warning lamp in his car. It doesn't stop the problem, it just hides it. – TLP Feb 23 '12 at 19:05
  • It's not a good solution but I don't know how to proceed. – user1039417 Feb 24 '12 at 01:05
  • You should narrow down the problem, try to solve it, and if you fail, ask a new question. – TLP Feb 24 '12 at 11:04

3 Answers3

1

It can be simplified somewhat (I also made some stylistic changes to improve readability):

my @data =  (
    [ qw /2012-02-21_09:43:43/ ],
    [ qw /2012-02-21_09:43:43/ ]
);
my %counts;   
foreach my $words (@data) {
    my ($day, $hour) = ($words->[0] =~ /(\d{4}-\d{2}-\d{2})_(\d+):/ );
    $counts{$day}->{$hour} += 1;
}
foreach my $day (keys %counts) {
    foreach my $hour (keys %{ $counts{$day} }) { 
        print "Hour count for $day:$hour is: $counts{$day}->{$hour}\n";
    }
}

The working part of the loop that is central to your query is this:

    my ($day, $hour) = ($words->[0] =~ /(\d{4}-\d{2}-\d{2})_(\d+):/ );

    # You don't need minutes/seconds, so don't match them
    # On the other hand, it's better to match YYYY/MM/DD explicitly!
    # A regexp match in a list context will return a list of captures! 
    #     e.g. ($1, $2, ...)

    $counts{$day}->{$hour} += 1;
    # You need to merely add 1 to a value. No need to push ones on a list.

    # Please note that if the data is not guaranteed to be perfectly formatted, 
    # you need to defend against non-matches:
    $counts{$day}->{$hour} += 1 if (defined $day && defined $hour);

Here's the same code with comments added clarifying why I made the stylistic changes:

my @data =  (  # Don't use @a - variable name should have meanings
    [ qw /2012-02-21_09:43:43/ ], # Not sure why you are using an array ref with
    [ qw /2012-02-21_09:43:43/ ], #   just 1 element, but let's pretend that is OK
);
my %counts;   
foreach my $words (@data) { # Almost never rely on $_ - less readable
    my ($day, $hour) = ($words->[0] =~ /(\d{4}-\d{2}-\d{2})_(\d+):/ ;
    $counts{$day}->{$hour} += 1; # You can omit "->" but that's less readable
}
foreach my $day (keys %counts) { # Always localize your variable to the block they need
    foreach my $hour (keys %{ $counts{$day} }) { 
        print "Hour count for $day:$hour is: $counts{$day}->{$hour}\n";
    }
}
Udo Held
  • 12,314
  • 11
  • 67
  • 93
DVK
  • 126,886
  • 32
  • 213
  • 327
  • So that is how to reference the hash properly, I immediately thought of it just as I was falling asleep! And I forgot about the addition operator. Thanks, filling in the gaps in the knowledge, one line at a time. – user1039417 Feb 21 '12 at 21:45
1

You should consider using a module to parse your time stamps, such as DateTime::Format::Strptime.

use DateTime::Format::Strptime;

my $strp = new DateTime::Format::Strptime( 
    pattern => "%Y-%m-%d_%H:%M:%S" 
);

my $t = $strp->parse_datetime("2012-02-21_09:43:43"); 

my $year  = $t->year;
my $month = $t->month;
my $day   = $t->day;
# ...etc

If you were to do something like:

for my $aref (@a) {
    for my $line (@$aref) {         # Note: better than $line = @$_[0]
        my $t = $strp->parse_datetime($line);
        my $key = sprintf "%s-%s", $t->year, $t->month;
        push @{$count{$key}}, $t;   # save the whole object in the array
    }
}

for my $key (sort keys %count) {
    my $count = @{$count{$key}};    # get size of array
    for my $obj (@{$count{$key}}) { # list all the DateTime objects
        my $hour  = $obj->hour;
        # etc ...
    }
}

You could store all the data from the timestamps into DateTime objects, and use it later as required.

TLP
  • 66,756
  • 10
  • 92
  • 149
  • I don't manage the system with this Perl instance but that module does look like the best method of parsing the time stamps. Thanks I will give it a go. – user1039417 Feb 21 '12 at 21:40
0

There is a problem with your regex to get the date. As the date contains the character - you cant get the whole date with \d+ Instead you should use \S+ so that you get the whole date. I am trying your code now...will update with further info

Update 1

I assume that you want to get the count per day and per hour.So tweaked the logic little bit

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my ( @a, $line, %count, $day, $hour, $min, $sec ) = ();

@a =  ( 
[ qw /2012-02-21_09:43:43/ ],
[ qw /2012-02-21_09:43:43/ ],
[ qw /2012-02-21_09:43:44/ ],
[ qw /2012-02-21_09:43:44/ ],
[ qw /2012-02-21_09:43:44/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:45/ ],
[ qw /2012-02-21_09:43:47/ ],
[ qw /2012-02-21_09:43:47/ ],
[ qw /2012-02-21_09:43:49/ ],
[ qw /2012-02-21_10:43:49/ ],
);

foreach (@a) {
    $line = @$_[0] ;
    $line =~ /(\S+)_(\d+):(\d+):(\d+)/ ;

    $day    = $1;
    $hour   = $2;
    $min    = $3;
    $sec    = $4;

    #$count{$day} += 1;
    $count{$day}{$hour} += 1;
}

#print "Val is:".$count{$day}{$hour}."\n";

print Dumper (%count) . "\n";
foreach $day(keys%count)
{
    #print "Day count $day is:".$count{$day}."\n";
    foreach $hour(keys %{ $count{$day} })
    {
        print "Hour count $hour is:".$count{$day}{$hour}."\n";
    }
}
Raghuram
  • 3,937
  • 2
  • 19
  • 25
  • I used that regex as I only wanted to grab the entry for day but I think yours is better. Think I will go with the cpan module and play with that, but thanks for the help. – user1039417 Feb 21 '12 at 21:48