改进如何计算数组散列中的重复元素

时间:2012-01-22 00:36:28

标签: perl hash duplicates

我的任务是遍历一个列表(最多50K)的主机名和相关的IP和MAC地址,寻找重复项,试图做一点整理,我想出了这个有效的解决方案:

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


my %HoA = (
    host1   => [ "10.1", "ae:ab" ],
    host2   => [ "10.2", "aa:ee" ],
    host3   => [ "10.3", "aa:ee" ],
    host4   => [ "10.1", "ab:ab" ],
);

my %duplicate =();

foreach my $key ( keys %HoA ) {
  push @{ $duplicate { $HoA{$key}[0] } } , "$key", "$HoA{$key}[1]" ;
  push @{ $duplicate { $HoA{$key}[1] } } , "$key", "$HoA{$key}[0]" ;
}

foreach my $key2 ( keys %duplicate ) {
    if ( (scalar @{ $duplicate{$key2} } ) > 2  ) {
        print "Duplicate:$key2\tGroup:@{ $duplicate{$key2} }\n";
    }
}


print Dumper (\%duplicate) . "\n";

我找不到任何在数组哈希中找到重复的例子,所以想出了上面的例子,这对我列出的四个条目非常有效。

所以我想知道是否有更好的方法可以做到这一点,以及我的代码如何扩展到大数?

欢迎任何见解。

干杯,

安迪

更新: 我最终选择了这个解决方案,(经过几周的游戏后)并添加了一个额外的匿名数组比较。

感谢所有评论,它真的有助于反复思考:

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

my (%dup) = ();
my ( $data_a, $data_b ) = ();
my ( @a,      @b )      = ();

@a = (
    [qw/ host1 10.1 ae:ab /], [qw/ host2 10.2 aa:ee /],
    [qw/ host3 10.3 aa:ee /], [qw/ host4 10.1 ab:ab /],
);

@b = (
    [qw/ host1 10.1 ae:ab /], [qw/ host3 10.2 aa:ee /],
    [qw/ host6 10.3 aa:ee /], [qw/ host4 10.1 ab:ab /],
);

foreach $data_a (@a) {
    my ( $host, $ip, $mac ) = @$data_a;
    push @{ $dup{$host} }, "$host $ip $mac";
    push @{ $dup{$ip} },   "$host $ip $mac";
    push @{ $dup{$mac} },  "$host $ip $mac";
}

foreach $data_b (@b) {
    my ( $host, $ip, $mac ) = @$data_b;
    push @{ $dup{$host} }, "$host $ip $mac";
    push @{ $dup{$ip} },   "$host $ip $mac";
    push @{ $dup{$mac} },  "$host $ip $mac";
}

print Dumper (%dup) . "\n";
#skipped scalar search

3 个答案:

答案 0 :(得分:1)

简单地发现重复项可以更加简洁,但是如果您需要显示具有多个条目的组的所有成员,那么您就可以接近最佳状态。但是,我会说,%HoA是哈希的一个坏名称,因为它应该描述哈希的内容而不是其结构。我也希望看到像这样拉出的哈希元素值

foreach my $key ( keys %HoA ) {
  my $val = $HoA{$key};
  push @{ $duplicate { $val->[0] } } , "$key", "$val->[1]" ;
  push @{ $duplicate { $val->[1] } } , "$key", "$val->[0]" ;
}

最后,您的%HoA实际上只是一组记录,每个记录包含三个值,并且可以很容易地包含在匿名数组的数组中。此代码与您的原始代码相同,我认为更具可读性

my @data = (
  [qw/ host1 10.1 ae:ab / ],
  [qw/ host2 10.2 aa:ee / ],
  [qw/ host3 10.3 aa:ee / ],
  [qw/ host4 10.1 ab:ab / ],
);

my %duplicate = ();

foreach my $rec ( @data) {
  my ($host, $val1, $val2) = @$rec;
  push @{$duplicate {$val1}} , "$host", "$val2" ;
  push @{$duplicate {$val2}} , "$host", "$val1" ;
}

答案 1 :(得分:1)

你已经得到了它。检测重复项的最佳方法是创建哈希。你只是在你的结构中循环两次,效率相当高。即使是一百万条记录也需要不到一秒的时间才能在现代计算机上执行。当你进行循环循环时,会发生缩放的主要问题。

例如,如果您决定将每个键与其他键进行比较,该怎么办:

foreach my $key ( keys %HoA ) {
    foreach my $key2 (keys %HoA) {
       #Some sort of comparison between $HoA{$key} and $HoA{$key2}
    }
}

这将循环比较%HoA中条目数的平方。相比之下,您的算法只循环两次键的数量(每个循环一次)。您的算法可能在不到一秒的时间内完成1,000,000个条目。循环循环可能需要一天时间。

我唯一的评论涉及可读性:​​

  • 您为什么使用$key$key2?我花了几微秒才意识到你不需要$key$key2
  • 我会使用两个单独的哈希而不是带有两个哈希的数组,我会将IP地址和MAC地址分配给两个临时变量。它简化了语法并使其更易于阅读。

例如:

my %ip_hash;
my %mac_hash;
foreach my $key ( keys %HoA ) {
    my $ip = $HoA{$key}[0];
    my $mac = $HoA{$key}[1];
    push @{ $ip_hash{$ip} }, $key, $mac;
    push @{ $mac_hash{$mac} }, $key, $ip;
}

我最初错过了将MAC地址放入IP哈希,将IP地址放入MAC哈希的事实。这里很清楚。

答案 2 :(得分:0)

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

my %hosts = (
  host1 => [ "10.1", "ae:ab" ],
  host2 => [ "10.2", "aa:ee" ],
  host3 => [ "10.3", "aa:ee" ],
  host4 => [ "10.1", "ab:ab" ],
);

my (%dup_mac,%dup_ip);

while( my($hostname,$addr) = each %hosts ) {
  push @{ $dup_ip{  $addr->[0] } }, $hostname;
  push @{ $dup_mac{ $addr->[1] } }, $hostname;
}

find_dup(\%dup_mac,'MAC');
find_dup(\%dup_ip,'IP');

sub find_dup{
  my($hash,$type) = @_;
  for my $addr ( sort keys %$hash ){
    my $hosts = $hash->{$addr};
    next unless @$hosts > 1;

    print "Duplicate $type: $addr\n";
    print ' 'x4, $_, "\n" for sort @$hosts;
  }
}
Duplicate MAC: aa:ee
    host2
    host3
Duplicate IP: 10.1
    host1
    host4