Perl zählt Protokolleinträge pro Sekunde unter Verwendung eines Hashs eines Hashs von Arrays

StackOverflow https://stackoverflow.com/questions/9376212

  •  28-10-2019
  •  | 
  •  

Frage

Aktualisiert:

Nach meinem ersten Beitrag und meinen Antworten habe ich einen weiteren Riss bekommen und meine Ziele und Ergebnisse etwas klarer geschrieben:

Ziel:

Ich versuche, die Anzahl der Treffer in einer Suchzeichenfolge einer Protokolldatei zu zählen, um herauszufinden, wie viele Vorkommen einer Nachricht auf folgende Weise generiert werden:

  • Gesamt pro Tag.
  • Gesamt pro Stunde.
  • Höchste pro Minute und Stunde.
  • Höchste pro Sekunde und Stunde.

    Mein Arbeitscode:

    #!/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;
    

    Ergebnisse

    Ich musste mich wegen meiner schlechten Hash-Dereferenzierungsmethoden strikt ausschalten (wofür ich mich schäme).

    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
    

    Versuch der Ausgabe:
    2012-02-21  12
     09:00      11
      09:43     9
       09:43:45 4   
    
     10:00      1
      10:44     1
       10:44:49 1
    

    Fragen:
    1. Gibt es eine bessere Möglichkeit, den Code zu schreiben und strikt einzuschalten?
    2. Wie würde ich vorgehen, um das höchste Vorkommen eines Hash-Werts innerhalb eines Hash aufzulisten, um nur die höchste Anzahl von Hashs aufzulisten?

      Danke für all die vorherigen Beiträge, ohne sie wäre ich nicht so weit gekommen.

      Prost,

      Andy

War es hilfreich?

Lösung

Es kann etwas vereinfacht werden (ich habe auch einige stilistische Änderungen vorgenommen, um die Lesbarkeit zu verbessern):

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";
    }
}

Der Arbeitsteil der Schleife, der für Ihre Abfrage von zentraler Bedeutung ist, ist folgender:

    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);

Hier ist derselbe Code mit Kommentaren, die verdeutlichen, warum ich die stilistischen Änderungen vorgenommen habe:

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";
    }
}

Andere Tipps

Sie sollten in Betracht ziehen, ein Modul zum Analysieren Ihrer Zeitstempel zu verwenden, z. B. 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

Wenn Sie etwas tun würden wie:

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 ...
    }
}

Sie können alle Daten aus den Zeitstempeln in DateTime-Objekten speichern und später nach Bedarf verwenden.

Es gibt ein Problem mit Ihrer Regex, um das Datum zu erhalten. Da das Datum das Zeichen enthält, können Sie mit \ d + nicht das gesamte Datum abrufen Stattdessen sollten Sie \ S + verwenden, damit Sie das gesamte Datum erhalten. Ich versuche jetzt Ihren Code ... wird mit weiteren Informationen aktualisiert

Update 1

Ich gehe davon aus, dass Sie die Anzahl pro Tag und pro Stunde erhalten möchten. Also haben Sie die Logik ein wenig optimiert

#!/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";
    }
}

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top