标识一个哈希数组中的元素,而不是另一哈希数组中的元素(perl)

时间:2019-03-08 21:27:18

标签: arrays database perl mariadb

我是perl的新手程序员,试图确定哪些元素在一个哈希数组中,而不在另一个哈希数组中。我正在尝试搜索“新”数组,标识id,标题和创建的元素,这些元素在“旧”数组中不存在。

我相信我可以通过一组基本的for()循环来使用它,但是我想更高效地完成它。这只是在尝试使用grep()之后失败的。

这些数组是从数据库构建的,例如:

use DBI;
use strict;
use Data::Dumper;
use Array::Utils qw(:all);
sub db_connect_new();
sub db_disconnect_new($);
sub db_connect_old();
sub db_disconnect_old($);

my $dbh_old   = db_connect_old();
my $dbh_new   = db_connect_new();

# get complete list of articles on each host first (Joomla! system)
my $sql_old   = "select id,title,created from mos_content;"; 
my $sql_new   = "select id,title,created from xugc_content;";

my $sth_old   = $dbh_old->prepare($sql_old);
my $sth_new   = $dbh_new->prepare($sql_new);

$sth_old->execute();
$sth_new->execute();

my $ref_old;
my $ref_new;

while ($ref_old = $sth_old->fetchrow_hashref()) {
  push @rv_old, $ref_old;
}

while ($ref_new = $sth_new->fetchrow_hashref()) {
  push @rv_new, $ref_new;
}

my @seen = ();
my @notseen = ();
foreach my $i (@rv_old) {
   my $id = $i->{id};
   my $title = $i->{title};
   my $created = $i->{created};
   my $seen = 0;
   foreach my $j (@rv_new) {
      if ($i->{id} == $j->{id}) {
         push @seen, $i;
         $seen = 1;
      }
   }
   if ($seen == 0) {
       print "$i->{id},$i->{title},$i->{state},$i->{catid},$i->{created}\n";
      push @notseen, $i;
   }
}

使用Dumper(@rv_old)打印它们时,数组如下所示:

$VAR1 = {
          'title' => 'Legal Notice',
          'created' => '2004-10-07 00:17:45',
          'id' => 14
        };
$VAR2 = {
          'created' => '2004-11-15 16:04:06',
          'id' => 86096,
          'title' => 'IRC'
        };
$VAR3 = {
          'id' => 16,
          'created' => '2004-10-07 16:15:29',
          'title' => 'About'
        };

我试图通过数组引用使用grep(),但是我认为我对数组,哈希和引用的理解不够好,无法正确执行。我的grep()失败尝试如下。我将对任何如何正确执行此操作的想法表示感谢。

我相信这是我不知道如何在第二个哈希数组中引用id字段的问题。我看到的大多数使用grep()的示例都只是遍历整个数组,就像使用常规grep(1)一样。我需要遍历一个数组,检查id字段中的每个值与另一个数组中的id字段。

  my $rv_old_ref        = \@rv_old;
  my $rv_new_ref        = \@rv_new;

  for my $i ( 0 .. $#rv_old) {
    my $match = grep { $rv_new_ref->$_ == $rv_old_ref->$_ } @rv_new;
    push @notseen, $match if !$match;
  }

我还尝试了上述grep()的变体:

1) if (($p) = grep ($hash_ref->{id}, @rv_old)) {
2) if ($hash_ref->{id} ~~ @rv_old) {

3 个答案:

答案 0 :(得分:3)

有许多比较数组的库。但是,您的比较涉及复杂的数据结构(数组具有hashrefs作为元素),这至少使我知道的所有模块的使用变得复杂。

因此,这是一种手工完成的方法。我使用显示的数组及其副本,但更改了一个值。

use warnings;
use strict;
use feature 'say';

use List::Util qw(none);   # in List::MoreUtils with older Perls
use Data::Dump qw(dd pp);

sub hr_eq {
    my ($e1, $e2) = @_; 
    return 0 if scalar keys %$e1 != scalar keys %$e2;
    foreach my $k1 (keys %$e1) {
       return 0 if !exists($e2->{$k1}) or $e1->{$k1} ne $e2->{$k1};            
    }   
    return 1
}

my @a1 = ( 
    { 'title' => 'Legal Notice', 'created' => '2004-10-07 00:17:45', 'id' => 14 },
    { 'created' => '2004-11-15 16:04:06', 'id' => 86096, 'title' => 'IRC' },  
    { 'id' => 16, 'created' => '2004-10-07 16:15:29', 'title' => 'About' }
);        
my @a2 = ( 
    { 'title' => 'Legal Notice', 'created' => '2004-10-07 00:17:45', 'id' => 14 },
    { 'created' => '2004-11-15 16:xxx:06', 'id' => 86096, 'title' => 'IRC' },  
    { 'id' => 16, 'created' => '2004-10-07 16:15:29', 'title' => 'About' }
);

my @only_in_two = grep { 
    my $e2 = $_; 
    none { hr_eq($e2, $_) } @a1;
} @a2;

dd \@only_in_two;

这可以正确地标识@a2中不存在的元素@a1(在时间戳中带有xxx)。

注释

  • 这将查找一个数组中的哪些元素不在另一个数组中,而不是数组之间的全部差异。这就是问题的具体要求。

  • 比较取决于您的数据结构(hashref)的详细信息;除非您想获得更全面的库(例如Test::More),否则无法逃避这一点。

  • 这将使用字符串比较ne,即使是数字和时间戳也是如此。看看对特定元素使用更适当的比较对您的真实数据是否有意义。

  • 在整个列表中搜索列表的每个元素是一种 O(N * M)算法。只要数据不太大,这种(二次)复杂性的解决方案就可以使用。但是,一旦数据变得足够大,以至于大小增加具有明显的效果,它们就会迅速崩溃(减速到无用的地步)。时间到您的情况来感受一下。

    这里存在一种利用散列的 O(N + M)方法,如池上答案所示。一旦数据足够大以至于无法显示,这在算法上会更好。但是,由于您的数组带有复杂的数据结构(hashrefs),因此需要一些工作来提出一个有效的程序,特别是因为我们不知道数据。但是,如果您的数据相当大,那么您肯定要实现这一点。


一些关于过滤的评论。

该问题正确地观察到,对于数组的每个元素(在grep中进行处理),都需要检查整个其他数组。

使用List::Util中的grepnone的正文中完成此操作。如果该代码块中的代码对列表的所有元素都为false,则返回true;否则返回true。因此,如果“没有”元素满足该代码。这是要求的核心:不得在另一个数组中找到一个元素。

默认$_需要护理,因为grepnone都使用它。

grep的块$_中,别名为列表中当前处理的元素,因为grep逐一遍历它们;我们将其保存到命名变量($e2)中。然后none出现,并在其块中“占有” $_,在处理过程中为其分配@a1的元素。 @a2的当前元素也可用,因为我们已经将其复制到$e2中。

none中执行的测试被拉到一个子例程中,我称之为hr_eq,以强调该子例程专门用于哈希值(元素)中的相等比较。

可以在此子目录中调整详细信息。首先,您可以为每个键添加自定义比较(数字必须使用ne,而不是为每个键直接使用==)。然后,如果您的数据结构发生变化,这就是您调整细节的地方。

答案 1 :(得分:2)

您可以使用grep

for my $new_row (@new_rows) {
   say "$new_row->{id} not in old"
      if !grep { $_->{id} == $new_row->{id} } @old_rows;
}

for my $old_row (@old_rows) {
   say "$old_row->{id} not in new"
      if !grep { $_->{id} == $old_row->{id} } @new_rows;
}

但这是一个O(N * M)解决方案,而存在一个O(N + M)解决方案会更快。

my %old_keys;  ++$old_keys{ $_->{id} } for @old_rows;
my %new_keys;  ++$new_keys{ $_->{id} } for @new_rows;

for my $new_row (@new_rows) {
   say "$new_row->{id} not in old"
      if !$old_keys{$new_row->{id}};
}

for my $old_row (@old_rows) {
   say "$old_row->{id} not in new"
      if !$new_keys{$old_row->{id}};
}

如果您的两个数据库连接都指向同一个数据库,则可以在数据库本身中更有效地完成此操作。

  1. 创建一个临时表,其中包含三个字段idold_countDEFAULT 0)和new_countDEFAULT 0)。
  2. INSERT OR UPDATE从旧表进入临时表,在此过程中将old_count递增。
  3. INSERT OR UPDATE从新表进入临时表,在此过程中将new_count递增。
  4. SELECT临时表中具有0的{​​{1}}或old_count的{​​{1}}的行。

答案 2 :(得分:0)

select id,title,created from mos_content
     LEFT JOIN xugc_content USING(id)
     WHERE xugc_content.id IS NULL;

为您提供mos_content中但不在xugc_content中的行。

这甚至比Perl代码还短。