首页 > 解决方案 > 识别一个哈希数组中不在另一个哈希数组中的元素(perl)

问题描述

我是一名新手 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) {

标签: arraysdatabaseperlmariadb

解决方案


有许多比较数组的库。但是,您的比较涉及复杂的数据结构(数组具有 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)方法,利用哈希,如 ikegami 答案所示。一旦数据大到足以显示,这在算法上要好得多。但是,由于您的数组带有复杂的数据结构(hashrefs),因此需要做一些工作才能提出一个工作程序,特别是因为我们不知道数据。但是,如果您的数据很大,那么您肯定想要实现它。


关于过滤的一些评论。

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

这是在grep使用nonefrom List::Util的主体中完成的。如果其块中的代码对列表的所有元素评估为 false,则返回 true;因此,如果“没有”元素满足该代码。这是要求的核心:不能在另一个数组中找到一个元素。

需要注意默认$_变量,因为grep和都使用它none

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

执行的测试none被拉入一个子程序,我调用hr_eq它是为了强调它专门用于hashrefs(元素中的元素)的相等比较。

正是在这个 sub 中可以调整细节。首先,您可以为特定键添加自定义比较(数字必须使用等),而不是直截了当地使用ne每个键的值。==然后,如果您的数据结构发生变化,这就是您需要调整细节的地方。


推荐阅读