我是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) {
答案 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中的grep
在none
的正文中完成此操作。如果该代码块中的代码对列表的所有元素都为false,则返回true;否则返回true。因此,如果“没有”元素满足该代码。这是要求的核心:不得在另一个数组中找到一个元素。
默认$_
需要护理,因为grep
和none
都使用它。
在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}};
}
如果您的两个数据库连接都指向同一个数据库,则可以在数据库本身中更有效地完成此操作。
id
,old_count
(DEFAULT 0
)和new_count
(DEFAULT 0
)。INSERT OR UPDATE
从旧表进入临时表,在此过程中将old_count
递增。INSERT OR UPDATE
从新表进入临时表,在此过程中将new_count
递增。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代码还短。