组合哈希键输出(哈希外连接)

Posted

技术标签:

【中文标题】组合哈希键输出(哈希外连接)【英文标题】:combine keys of hashes for output (outer join of hashes) 【发布时间】:2017-07-13 19:50:10 【问题描述】:

我正在使用 Perl 5.8.8 分析日志文件。[1] 我正在寻找暴露两种触发模式中的一些的日子,可能是其中之一,也可能是两者(我更改了代码 sn-p 中的实际模式如下所示)。我对每天的出现次数感兴趣,下一步将是制作一个电子表格,这就是使用制表符进行输出格式化的原因。

因为一天中可能只出现一种模式,所以我需要一种方法来组合两个哈希的键。我通过生成一个新的哈希来做到这一点。有内置功能吗?我搜索了网络和堆栈溢出没有任何结果,我在这里得到的唯一命中是Build a string from 2 hashes,但在那种情况下,密钥集是相同的。

#!/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/^(\d4-\d2-\d2)(.+)$/) 
        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 版本对于实际问题并不那么重要,至少我希望如此......

【问题讨论】:

不要命名 $a$b。这些变量保留用于sort 块。结合到底是什么意思?你能展示你想要的最终输出吗?我认为您选择了一种复杂的方法。这可以简化,但我们需要看看你想要什么。最有效的数据结构取决于最终产品。 是的,^ 说的。 @simbabque 当然,$a 和 $b 不好,已修复,感谢您指出这一点。 我听起来像个挑剔的人,但全大写的变量名并不好。它们看起来像常数。此外,单字母变量不会说话,因此很难猜测它们的用途。为什么不将该部分重写为say join "\t", $day, $pattern_a$day // 0, $pattern_b$day // 0;,然后您就不再需要该变量了。您也不需要使用那些括号(),因为您使用了低杆or 而不是||,在这种情况下这是错误的,因为如果已经有@987654331,它也会是错误的无论如何@在那个变量中。 //定义或,这样更好。 @Gerry,添加了 simbabque 示例输出。 【参考方案1】:

使用单个哈希不是更容易吗?

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

my %stats;

while (my $line = readline) 
    my ($day, $pattern) = $line =~ /^(\d4-\d2-\d2).*(beendet|ohne)/
        or next;

    $stats$day$pattern++;


for my $day (sort keys %stats) 
    printf "%s\t%d\t%d\n",
        $day,
        $stats$daybeendet // 0,
        $stats$dayohne // 0;

如果您使用的是 5.10 之前的 perl,请将 // 替换为 ||;在这种情况下,它没有有效的区别。 (但考虑升级:5.8.8 是从 2006 年开始的。现在已经十多年了。官方维护的 perl 版本是 5.22(2015)和 5.24(2016)。)

【讨论】:

easier to use a single hash -- 是的,也许,但我只是偶尔使用 Perl,所以我还是先习惯了... 你说,这个问题在使用Perl的时候问错了吗?也许这就是我一无所获的原因?我的意思是,输出 关联数据,这就是哈希的用途......(是的,我现在正在寻找更新的 Perl 版本) ...这比 simbabque 的第一个解决方案运行得慢,即使我级联匹配日期和匹配 /(beendet|ohne)/ 模式集中似乎很好(但如何避免输出重复?)【参考方案2】:

先按,然后按模式来组织数据更容易。这可以使用哈希引用来完成。

use strict;
use warnings;

my %matches;
while ( my $line = <DATA> ) 
    if ($line =~ m/^(\d4-\d2-\d2)(.+)$/) 
        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

要了解数据结构,您可以使用Data::Dumper 来输出它(尽管我建议改用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/^(\d4-\d2-\d2)(.+)$/) 
        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。

【讨论】:

对不起,我没有提到我正在使用 Perl 5(.8.8),也许这就是我在使用 say// 时遇到困难的原因? each %patterns 代码看起来已损坏。如果$pattern 匹配,则循环以last 中止,从而将%patterns 迭代器留在中间某处。下一个$line 将只尝试剩余的模式。 @Wolf 如果你有 5.8.8,你会错过很多好东西。 // 确实不存在。但是您可以改用||say 就像 print 附加了换行符。请在问题中包含该详细信息。 @Wolf 为什么你认为你需要切换?我想如果你有 5.8.8 你有一些旧的服务器带有一个古老的 Perl。如果您使用的是 Windows,ActivePerl 有一个非常新的版本,但您需要购买许可证才能在商业上使用它。 say 和 '//` 从 5.10 开始提供。您使用哪种 Perl 发行版并不重要,但您保持最新是有优势的。 :) 我建议使用print join( "\t", "Datum", @head ), "\n";print join( "\t", $day, map $matches$day-&gt;$_ || 0 @head), "\n";,多行版本看起来比使用map的版本更复杂。

以上是关于组合哈希键输出(哈希外连接)的主要内容,如果未能解决你的问题,请参考以下文章

Datavault:如何获取外键关系的哈希(填充链接表)

如何使用外键连接两个数据集以创建新数据集?

如果值相同,则连接哈希键,如果键相同,则连接哈希值

Java:哈希图中的复合键

什么是 Inner Like 连接?

Oracle 内连接和外连接有效组合