Комбинировать ключи хэшей для вывода (внешнее соединение хэшей)

Я анализирую файл журнала с Perl 5.8.8.[1] Я ищу дни, в которых раскрываются некоторые из двух шаблонов триггера, возможно, один из них, а может и оба (я изменил фактические шаблоны в фрагменте кода, показанном ниже). Меня интересует количество вхождений за день, следующим шагом будет создание из него электронной таблицы, поэтому форматирование вывода с вкладками.

Поскольку за один день может появиться только один из шаблонов, мне нужен способ объединить ключи обоих хэшей. Я сделал, генерируя новый хэш. Есть ли встроенная функция для этого? Я искал в Интернете и переполнение стека без какого-либо результата, единственный хит, который я получил здесь, был Построить строку из 2 хешей, но в этом случае наборы ключей были идентичны.

#!/usr/bin/perl -w
use strict;
use warnings;
use locale;

# input analysis: searching for two patterns:
my %pattern_a = ();
my %pattern_b = ();
foreach my $line (<>) {
    if ($line =~ m/^(\d{4}-\d{2}-\d{2})(.+)$/) {
        my $day = $1;
        my $what = $2;
        if ($what =~ m/beendet/) {
            $pattern_a{$day} ++;
        } elsif ($what =~ m/ohne/) {
            $pattern_b{$day} ++;
        }
    }
}

# generate the union of hash keys:        <-- In Question
my %union = ();
$union{$_} = 1 for keys %pattern_a;
$union{$_} = 1 for keys %pattern_b;

# formatted output sorted by day:
foreach my $day (sort keys %union) {
    print join "\t", $day, 
            ($pattern_a{$day} || 0), 
            ($pattern_b{$day} || 0)."\n";
}

Ожидаемый результат будет выглядеть так:

2017-02-01      0       1
2017-02-18      0       592
2017-02-19      2       0

[1] Я знаю, что эта версия Perl устарела. Но я использую Perl редко, но когда я это делаю, он должен идти быстро. Так что выяснение версий Perl и так далее будет сделано позже. Но версия Perl не так важна для реального вопроса, по крайней мере, я на это надеюсь...

2 ответа

Решение

Проще структурировать данные сначала по дням, а затем по шаблону. Это можно сделать, используя ссылку на хеш.

use strict;
use warnings;

my %matches;
while ( my $line = <DATA> ) {
    if ($line =~ m/^(\d{4}-\d{2}-\d{2})(.+)$/) {
        my $day = $1;
        my $what = $2;
        if ($what =~ m/beendet/) {
            $matches{$day}->{a} ++;
        } elsif ($what =~ m/ohne/) {
            $matches{$day}->{b} ++;
        }
    }
}

# formatted output sorted by day:
foreach my $day (sort keys %matches) {
    print join(
        "\t",
        $day,
        $matches{$day}->{a} || 0,
        $matches{$day}->{b} || 0,
    ), "\n";
}

__DATA__
2017-02-01 einmal Pommes ohne
2017-02-02 Wartung gestartet
2017-02-02 Wartung beendet
2017-02-03 ohne Moos nix los

Эта программа производит вывод следующим образом

2017-02-01  0   1
2017-02-02  1   0
2017-02-03  0   1

Чтобы понять структуру данных, вы можете использовать http://p3rl.org/Data::Dumper для вывода (хотя я предлагаю вместо этого использовать http://p3rl.org/Data::Printer, так как он предназначен для использования человеком, а не для сериализации).

use Data::Dumper;
print Dumper \%matches;
__END__

$VAR1 = {
          '2017-02-03' => {
                            'b' => 1
                          },
          '2017-02-02' => {
                            'a' => 1
                          },
          '2017-02-01' => {
                            'b' => 1
                          }
        };

Как видите, данные структурированы сначала по дате. Каждый ключ представляет один день. Внутри есть дополнительная ссылка на хеш, которая содержит только один ключ. Это шаблон. Позже мы повторяем день первым. Тогда мы получим

{
    'b' => 1
}

в первой итерации. Затем мы повторяем все шаблоны. Приведенная выше программа делает это не путем итерации, а путем явного указания каждого возможного ключа. Если это там, это используется. Если это не определено, это установлено в 0 с || оператор.


Программа может быть дополнительно упрощена для использования произвольных шаблонов. Если вам не важен порядок шаблонов в выводе, включите заголовок, и вы можете легко добавить больше шаблонов позже.

Я использовал хеш конфигурации для шаблонов и Text:: Table для создания выходных данных.

use strict;
use warnings;
use Text::Table;

my %matches;
my %patterns = (
    beendet => qr/beendet/,
    ohne    => qr/ohne/,
    komplex => qr/foo\sbar?/, # or whatever
);
while ( my $line = <DATA> ) {
    if ($line =~ m/^(\d{4}-\d{2}-\d{2})(.+)$/) {
        my $day = $1;
        my $what = $2;
        foreach my $name ( sort keys %patterns ) {
            if ( $what =~ $patterns{$name} ) {
                $matches{$day}->{$name}++ ;
                last;
            }
        }
    }
}

# formatted output sorted by day:
my @head = sort keys %patterns;
my $tb = Text::Table->new( 'Tag', @head );

foreach my $day (sort keys %matches) {
    $tb->load([ $day, map { $matches{$day}->{$_} || 0 } @head ]);
}

print $tb;

__DATA__
2017-02-01 einmal Pommes ohne
2017-02-02 Wartung gestartet
2017-02-02 Wartung beendet
2017-02-03 ohne Moos nix los

Это печатает

Tag        beendet komplex ohne
2017-02-01 0       0       1   
2017-02-02 1       0       0   
2017-02-03 0       0       1   

Если вы не хотите устанавливать дополнительный модуль, просто создайте файл CSV. Так как вы из Германии, я предлагаю точку с запятой ; в качестве разделителя, потому что немецкий Excel использует его по умолчанию.

Вот подробный пример того, как это сделать вместо Text::Table.

my @head = sort keys %patterns;
print join( ';', @head ), "\n";
foreach my $day (sort keys %matches) {
    my @cols;
    push @cols, $matches{$day}->{$_} || 0 for @head;
    print join ';', $day, @cols;
    print "\n";
}

И вывод

beendet;komplex;ohne
2017-02-01;0;0;1
2017-02-02;1;0;0
2017-02-03;0;0;1

Но вам также следует заглянуть в Text:: CSV, если вы не хотите, чтобы это отображалось на экране.

Не проще ли будет использовать один хеш?

#!/usr/bin/perl
use strict;
use warnings;

my %stats;

while (my $line = readline) {
    my ($day, $pattern) = $line =~ /^(\d{4}-\d{2}-\d{2}).*(beendet|ohne)/
        or next;

    $stats{$day}{$pattern}++;
}

for my $day (sort keys %stats) {
    printf "%s\t%d\t%d\n",
        $day,
        $stats{$day}{beendet} // 0,
        $stats{$day}{ohne} // 0;
}

Если вы используете Perl до 5.10, замените // от ||; в этом случае нет никакой разницы. (Но рассмотрите возможность обновления: 5.8.8 с 2006 года. Сейчас ему более десяти лет. Официально поддерживаемые версии perl - 5.22 (2015) и 5.24 (2016).)

Другие вопросы по тегам