如何在Perl中比较数组?

8

我有两个数组,@a@b。我想比较这两个数组中的元素。

my @a = qw"abc def efg ghy klm ghn";
my @b = qw"def ghy jgk lom com klm";

如果任何元素匹配,则设置一个标记。有没有简单的方法来做到这一点?

4
因为您的问题不清楚,所以会得到两种不同的答案:(1) 搜索成对匹配的答案,例如$a[$i] eq $b[$i];(2) 搜索任何匹配项的答案,例如$a[$i] eq $b[$j]。那么您的目标是什么? - FMc
可能是重复的问题,与https://dev59.com/0HI-5IYBdhLWcg3w6tJR相似。 - Sinan Ünür
@Sinan:你标记为“可能重复”的那个问题和这个不一样。那个问题是关于比较两个数组的所有元素,但这个问题是关于找到一个共同的元素。 - user181548
如果数组至少有一个元素不同,则它们不相同。如果数组相同,则它们不会在元素上有所不同。被接受的答案如果至少有一对不同则返回 0,如果没有则返回 1 - Sinan Ünür
12个回答

10

首先,你的两个数组需要正确书写。

@a = ("abc","def","efg","ghy","klm","ghn");
@b = ("def","efg","ghy","klm","ghn","klm");

其次,针对任意数组(例如其元素可能是对其他数据结构的引用),您可以使用Data::Compare

对于其元素为标量的数组,您可以使用List::MoreUtilspairwise BLOCK ARRAY1 ARRAY2进行比较,其中BLOCK是您的比较子例程。如果没有List::MoreUtils,则您可以通过以下方式模拟pairwise

if (@a != @b) {
    $equals = 0;
} else {
    $equals = 1;
    foreach (my $i = 0; $i < @a; $i++) {
        # Ideally, check for undef/value comparison here as well 
        if ($a[$i] != $b[$i]) { # use "ne" if elements are strings, not numbers
                                # Or you can use generic sub comparing 2 values
            $equals = 0;
            last;
        }
    }
}

顺便说一句,我不确定但List::Compare可能总是对列表进行排序。我不确定它是否可以进行成对比较。


你使用“标量(scalar)”的频率太过于随意,我不太喜欢。 - Sinan Ünür
1
这是非常主观的。我认为多余的废话会影响可读性(否则,我还会继续用Java编程;-))。 - Sinan Ünür
3
我还没有仔细检查它是否有效,但为什么 @a = qw"abc def efg ghy klm ghn" 不行呢?它不应该只是在用户自定义的分隔符 ('"' 在这种情况下) 内被解析为列表吗?它应该与 qw//qw||qw() 完全相同。 - Oesor
4
@Oesor - 你没有使用Sinan的Time::Machine模块。我的评论是针对问题最初的措辞(在brian编辑之前)说“my @a = "abc,def,efg,ghy,klm,ghn"`。 - DVK
qw"abc def efg ghy klm ghn" 是正确的。 - Christoffer Hammarström
显示剩余2条评论

7

列表::比较

if ( scalar List::Compare->new(\@a, \@b)->get_intersection ) {}

5

创建一个交集函数,它将返回存在于两个列表中的项目列表。然后,您的返回值取决于交集列表中的项目数量。

您可以在网络上轻松找到Perl的最佳intersect实现。我记得几年前就在寻找它。

以下是我找到的代码:

my @array1 = (1, 2, 3);
my @array2 = (2, 3, 4);
my %original = ();
my @isect = ();
map { $original{$_} = 1 } @array1; @isect = grep { $original{$_} } @array2;

2

对于“如果任何元素匹配”的要求,使用集合的交集:

sub set{
  my %set = map { $_, undef }, @_;
  return sort keys %set;
}
sub compare{
    my ($listA,$listB) = @_;
    return ( (set(@$listA)-set(@$listB)) > 0)
}

这个问题被标记为“perl”。我不想在未警告您的情况下对您进行投票。 - user181548
没有问题是完全特定于某种语言的。我相信有人可以想出它的 Perl 版本。它也阐明了一般性的观点。 - Phil H
3
是的,但是如果一个新手在没有任何通知的情况下看到您作为 Perl 标签线程中的答案,并输入该代码,然后想知道为什么它不起作用呢?对于这个回答唯一负责任的做法就是给它点个踩。抱歉。 - user181548
@Brad:谢谢,看起来很不错。我很惊讶没有内置/广泛可用的集合模块... - Phil H
我取消了我的踩。还有一个踩,但不是我踩的。 - user181548

2
这是一种方法:
use warnings;
use strict;
my @a = split /,/, "abc,def,efg,ghy,klm,ghn";
my @b = split /,/, "def,ghy,jgk,lom,com,klm";
my $flag = 0;
my %a;
@a{@a} = (1) x @a;
for (@b) {
    if ($a{$_}) {
        $flag = 1;
        last;
    }
}
print "$flag\n";

虽然不是最容易理解的内容,但我喜欢它。当回答问题时,我喜欢解释一些不太常见的行为方式,这样我们可以学到更多,而不是仅仅按照指南操作。 - HerbN
有人能解释一下"@a{@a} = (1) x @a;"的意思吗? - Jared Hooper
它将%a的键填充为@a的元素,而值全部为1。这利用了Perl允许使用相同名称的不同变量类型,哈希切片和标量上下文中的数组。但是,此代码无法用于比较具有非唯一值的数组。 - beasy

1
my @a = qw' abc def efg ghy klm ghn ';
my @b = qw' def ghy jgk lom com klm ';

my $flag;

foreach  my $item(@a) {
  $flag = @b~~$item ? 0 : 1;
  last if !$flag;
}

请注意,您需要使用Perl 5.10或更高版本才能使用智能匹配运算符~~)。

@DVK,我不知道。我是从《学习Perl》中学到的。我不需要使用“use 5.010;”语句,但也许只有在Perl 5.10之后才能使用。我会查一下的。我还是一个Perl学习者。如果有什么错误,请指正 :) - Mike
@DVK,书上说“Perl 5.10的智能匹配运算符”。但看起来我不必使用use 5.010;语句。我再次测试了一下,在WinXP上至少使用ActivePerl 5.10.0时,use 5.010;语句是不必要的。但我想在Perl 5.10之前这样做是行不通的。 - Mike
虽然我没有看到任何明显的新手错误,但我认为这个程序不会始终正确地工作。 - Brad Gilbert
需要使用$item ~~ @b吗?即使如此,如果数组具有相同的元素但顺序不同,它是否会返回true?如果不是,请解释一下代码! - U. Windl

0

如果您认为不同顺序的数组是不同的,您可以使用Array::Diff

if (Array::Diff->diff(\@a, \@b)->count) {
   # not_same
} else {
   # same
}

0

这是Perl语言。'显而易见'的解决方案:

my @a = qw"abc def efg ghy klm ghn";
my @b = qw"def ghy jgk lom com klm";
print "arrays equal\n"
    if @a == @b and join("\0", @a) eq join("\0", @b);

给定@a中不存在"\0"。

但感谢确认除了自己编写之外没有其他通用解决方案。


那么 @a == @b 只是一种性能优化,如果数组中有许多或大量元素,它是否会非常低效呢?例如,如果元素是引用,它能否正常工作? - U. Windl

0
my @a1 = qw|a b c d|;
my @a2 = qw|b c d e|;

for my $i (0..$#a1) {
    say "element $i of array 1 was not found in array 2" 
        unless grep {$_ eq $a1[$i]} @a2
}

0

对于小的 n,暴力破解应该可以解决问题:

my $flag = 0;
foreach my $i (@a) {
    foreach my $k (@b) {
        if ($i eq $k) {
            $flag = 1;
            last;
        }
    }
}

对于大的n,使用哈希表:

my $flag   = 0;
my %aa     = ();
   $aa{$_} = 1 foreach (@a);
foreach my $i (@b) {
    if ($aa{$i}) {
        $flag = 1;
        last;
    }
}

当一个很大的n|@a| + |@b| > ~1000项时


如果 |@a| + |@b| == 1000,它可能意味着 |@a| == 2 && |@b| == 998 或者 |@a| == 500 && |@b| == 500。然而,在前一种情况下,比较的次数将是1996,而在后一种情况下,比较的次数将是250000。因此,基本上重要的不是总和,而是乘积。 - U. Windl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接