在Perl中,是否有一种内置的方法来比较两个数组是否相等?

55
我有两个字符串数组,我想要比较它们是否相等。
my @array1 = ("part1", "part2", "part3", "part4");
my @array2 = ("part1", "PART2", "part3", "part4");

有没有内置的方法可以像标量一样比较数组? 我尝试过:
if (@array1 == @array2) {...}

但它只是在标量上下文中评估每个数组,因此比较了每个数组的长度。
我可以自己编写一个函数来完成这个操作,但这似乎是一个低级操作,应该有一种内置的方法来完成。有吗?
编辑:不幸的是,我无法访问5.10+或可选组件。
15个回答

57

现在有了新的智能匹配运算符

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

my @x = (1, 2, 3);
my @y = qw(1 2 3);

say "[@x] and [@y] match" if @x ~~ @y;

关于Array::Compare

在内部,比较器使用join将两个数组转换为字符串,并使用eq比较字符串。

我猜这是一种有效的方法,但只要我们使用字符串比较,我更愿意使用类似于以下的内容:

#!/usr/bin/perl

use strict;
use warnings;

use List::AllUtils qw( each_arrayref );

my @x = qw(1 2 3);
my @y = (1, 2, 3);

print "[@x] and [@y] match\n" if elementwise_eq( \(@x, @y) );

sub elementwise_eq {
    my ($xref, $yref) = @_;
    return unless  @$xref == @$yref;

    my $it = each_arrayref($xref, $yref);
    while ( my ($x, $y) = $it->() ) {
        return unless $x eq $y;
    }
    return 1;
}

如果你要比较的数组很大,拼接它们会比逐个比较每个元素要消耗更多的内存并且需要进行大量的工作。

更新:当然,应该测试这样的说法。简单的基准测试:

#!/usr/bin/perl

use strict;
use warnings;

use Array::Compare;
use Benchmark qw( cmpthese );
use List::AllUtils qw( each_arrayref );

my @x = 1 .. 1_000;
my @y = map { "$_" } 1 .. 1_000;

my $comp = Array::Compare->new;

cmpthese -5, {
    iterator => sub { my $r = elementwise_eq(\(@x, @y)) },
    array_comp => sub { my $r = $comp->compare(\(@x, @y)) },
};

这是最坏的情况,elementwise_eq需要逐一比较两个数组中的每个元素1,000次,并且结果如下:

             Rate   iterator array_comp
iterator    246/s         --       -75%
array_comp 1002/s       308%         --

另一方面,最好的情况是:

my @x = map { rand } 1 .. 1_000;
my @y = map { rand } 1 .. 1_000;
              率 array_comp  迭代器
array_comp   919/s         --       -98%
iterator   52600/s      5622%         --

iterator 的性能下降相当快:

my @x = 1 .. 20, map { rand } 1 .. 1_000;
my @y = 1 .. 20, map { rand } 1 .. 1_000;
              比率    迭代器 数组推导
迭代器     10014/s         --       -23%
数组推导   13071/s        31%         --

我没有考虑内存利用。


2
@Bill 谢谢你,但是这个分析不够透彻。它只是做了最基本的教导我不要在没有测量的情况下做出假设,并且在大多数情况下,你可以使用 Array::Compare - Sinan Ünür
2
简单的 my $i;for my $e (@$xref) {return unless $e eq $yref->[$i++];} 在 v5.14.2 中速度要快得多。 - Hynek -Pichi- Vychodil
13
Perl发布的5.18版于2013年5月发布,其中弃用了Smart Operator。 - Flimm
1
@Flimm,智能匹配的“不那么聪明”的替代方案还有一些,它们仍然可以进行“全面”的匹配。到目前为止,我最喜欢的之一是match::simple - G. Cito

27

Test::More 的 is_deeply() 函数,它还会显示结构差异的确切位置,或者使用 Test::Deep 的 eq_deeply(),它不需要测试套件(只返回 true 或 false)。


3
除非是在测试中,否则不应该使用专为测试而设计的模块。请注意保持原意并尽量让翻译通俗易懂。 - Arc
@Arc 但是你可以拿走他们的子程序并重新利用它们。代码就在那里。 - undefined

13

虽然没有内置的函数,但是可以使用Array::Compare模块。

我认为,由于教学原因,这是Perl核心中被省略的操作之一——也就是说,如果你想要做这个操作,可能存在某些问题。我认为最具有说明性的例子是缺少核心的read_entire_file函数;基本上,在核心中提供该函数会让人们认为这是一个好主意,但相反,Perl的设计方式温和地引导你逐行处理文件,这通常更有效率、更好,然而初学者很少能够理解,并需要一些鼓励才能达到这个目标。

同样的道理适用于这里:通过比较两个数组来确定您尝试实现的目标可能有更好的方法。不一定是如此,但可能存在更好的方法。因此,Perl会引导您考虑其他达成目标的方式。


2
尽管 Perl 是一种表达能力很强的语言,但它在让你思考自己在做什么方面做得非常出色。 - Jamison Dance
1
在核心中提供“dump”函数是否意味着随机死亡并留下一个核心转储是个好主意? - jrockway
@chaos - 这是一个有趣的观点。我正在尝试将我的位置在Xml层次结构中与从文本文件中获取的数组进行匹配。(不幸的是,没有XPath模块。) - Bill
我不喜欢 Array::Compare,因为它的工作方式:它使用 "\x{07}" 作为分隔符将元素连接在一起,并比较生成的字符串。你可以指定一个不同的分隔符,但是如果不能保证特定字符不会出现在数组的输入中,则这种方法存在缺陷。 - Flimm
1
没有特殊的read_entire_file,因为你已经拥有完成它所需的一切。取消输入记录分隔符并读取一行。 - undefined

7
Perl 5.10为您提供了智能匹配运算符。
use 5.010;

if( @array1 ~~ @array2 )
{
    say "The arrays are the same";
}

否则,就像你所说的那样,你必须自己动手。

4
实际上,这并不能告诉你这些数组是否相同,但可以告诉你它们是否是可比较的 - Sinan Ünür
3
更糟糕的是,这取决于您使用的是5.10.0还是5.10.1+。在5.10.0中,它确实会检查数组是否相等。 - hobbs
7
我非常不喜欢使用“可比较”的词来描述智能匹配数组的行为。我会自动将其解读为“能够被比较”(这种行为相当无用),而不是“等同”的意思(在我看来,这个术语更好)。 - Michael Carman
3
智能运算符在Perl 5.18版本中已被弃用,该版本于2013年5月发布。 - Flimm
这个不起作用是因为智能匹配并不按照任何人对真实任务的想法工作(插入JeffGoldblum的梗)。试试用这两个数组:my @array1 = ('abc', 'def'); my @array2 = (qr/abc/, 'def'); 这些是“匹配”的,因为它尝试 $array1[0] ~~ $array2[0],即 'abc' =~ /abc/。现在,试试 @array2 ~~ @array1。现在不匹配了,因为顺序很重要。这只是一个简单的情况,它就会失败。 - undefined

7
只要您使用的是Perl 5.10或更高版本,就可以使用智能匹配运算符。请参阅智能匹配详解
if (@array1 ~~ @array2) {...}

4
实际上,这并不会告诉您数组是否相同,而是它们是否可比较 - Sinan Ünür
请参阅“智能匹配详解”(http://perldoc.perl.org/perlsyn.html#Smart-matching-in-detail)以了解“可比较”的更多细节。通常情况下,它意味着“是的,它们基本上是相同的”。 - Robert P
4
智能运算符在Perl 5.18版本中已被弃用,该版本于2013年5月发布。 - Flimm
1
这个不起作用是因为智能匹配并不按照任何人对真正任务的想法来工作(插入JeffGoldblum的梗)。试试用这两个数组:my @array1 = ('abc', 'def'); my @array2 = (qr/abc/, 'def'); 这些是"匹配"的,因为它尝试 $array1[0] ~~ $array2[0],即 'abc' =~ /abc/。现在,试试 @array2 ~~ @array1。现在不匹配了,因为顺序很重要。这只是一个简单的情况,它就会出错。 - undefined

6
更简单的解决方案更快:
#!/usr/bin/perl

use strict;
use warnings;

use Array::Compare;
use Benchmark qw( cmpthese );
use List::AllUtils qw( each_arrayref );

my @x = 1 .. 1_000;
my @y = map { "$_" } 1 .. 1_000;

my $comp = Array::Compare->new;

cmpthese -2, {
    iterator => sub { my $r = elementwise_eq(\(@x, @y)) },
    my_comp => sub { my $r = my_comp(\(@x, @y)) },
    array_comp => sub { my $r = $comp->compare(\(@x, @y)) },
};

@x = 1 .. 20, map { rand } 1 .. 1_000;
@y = 1 .. 20, map { rand } 1 .. 1_000;

cmpthese -2, {
    iterator => sub { my $r = elementwise_eq(\(@x, @y)) },
    my_comp => sub { my $r = my_comp(\(@x, @y)) },
    array_comp => sub { my $r = $comp->compare(\(@x, @y)) },
};

sub elementwise_eq {
    my ($xref, $yref) = @_;
    return unless  @$xref == @$yref;

    my $it = each_arrayref($xref, $yref);
    while ( my ($x, $y) = $it->() ) {
        return unless $x eq $y;
    }
    return 1;
}

sub my_comp {
    my ($xref, $yref) = @_;
    return unless  @$xref == @$yref;

    my $i;
    for my $e (@$xref) {
        return unless $e eq $yref->[$i++];
    }
    return 1;
}

perl 5, version 14, subversion 2 (v5.14.2) built for x86_64-linux-gnu-thread-multi 中的结果是:

             Rate   iterator array_comp    my_comp
iterator   1544/s         --       -67%       -80%
array_comp 4697/s       204%         --       -41%
my_comp    7914/s       413%        68%         --
               Rate   iterator array_comp    my_comp
iterator    63846/s         --        -1%       -75%
array_comp  64246/s         1%         --       -75%
my_comp    252629/s       296%       293%         --

3

这个问题已经变成了一个非常有用的资源。++ 为基准和讨论。

正如其他人指出的那样,智能匹配功能存在问题,并正在以其当前形式逐步淘汰。有一些替代方案是“不太聪明”的(因此避免了问题),并且很小,相当快速,并且没有太多的非核心依赖关系。

你可以通过查看@brian d foy的一些博客文章以及@rjbs的2011年2012年的~~未来历史的讨论链接。

比较数组可以简单而有趣!

use v5.20;   
use match::smart; 
my @x = (1, 2, 3);       
my @y = qw(4 5 6);    
my @z = qw(4 5 6);   
say \@x |M| \@y ? "[\@x] and [\@y] match": "no match";  
say \@y |M| \@z ? "[\@y] and [\@z] match": "no match";

__END__                              
@y and @z match, @x and @y do not

如果数组很简单,那么比较会特别有趣。但是数组可能是一件复杂的事情,有时您希望从比较结果中获得不同类型的信息。为此,Array::Compare 可以使微调比较更容易。


最佳智能匹配笑话要归功于@rjbs:“<fellow>…” :-) 请参考p5p帖子以了解上下文。 - G. Cito
1
match::simple 无法比较数组。对于 my @x = (1, 2, 3)my @y = (4, 5, 6)@x |M| @y 是真的,因为操作数在标量上下文中被评估。只要项目数量相等,它们就匹配。至于数组引用,文档 中说:“如果右侧是一个数组引用,则运算符会递归进入数组,如果左侧匹配任何数组元素,则匹配成功。” 这也不是我们需要的。 - x-yuri
1
match::smart 会适当地处理这个问题(递归地)。 - x-yuri
@x-yuri 谢谢,已修复示例。 - G. Cito

3

使用 List::Util::all 的仅核心解决方案:

use List::Util qw(all);

if (@array1 == @array2 && all { $array1[$_] eq $array2[$_] } 0..$#array1) {
    print "matched\n";
}

作为子程序:

# call me like string_array_equals([@array1], [@array2])
sub string_array_equals {
    my ($array1, $array2) = @_;

    @$array1 == @$array2 and
    all { $array1->[$_] eq $array2->[$_] } 0..$#$array1;
}

如果您需要自定义比较:
# call me like array_equals { $a eq $b } [@array1], [@array2]
sub array_equals(&$$) {
    my ($compare, $array1, $array2) = @_;

    @$array1 == @$array2 and
    all {
        local $a = $array1->[$_];
        local $b = $array2->[$_];
        $compare->($a, $b);
    } 0..$#$array1;
}

目前来说,all 并没有节省多少空间,您可以使用 for 来实现:

# call me like array_equals { $a eq $b } [@array1], [@array2]
sub array_equals(&$$) {
    my ($compare, $array1, $array2) = @_;

    @$array1 == @$array2 or return 0;
    for (0..$#$array1) {
        local $a = $array1->[$_];
        local $b = $array2->[$_];
        $compare->($a, $b) or return 0;
    }
    1;
}

编辑:List::Util::first可作为旧版perl(< v5.20)的替代品。

use List::Util qw(first);

if (@array1 == @array2 && !defined first { $array1[$_] ne $array2[$_] } 0..$#array1) {
    print "matched\n";
}

2

Data::Cmp 是另一个最近的选择。 cmp_data() 函数的操作类似于 cmp 运算符(请参阅perlop 以了解 cmp 的用法)。

示例:

use 5.10;
use Data::Cmp qw/cmp_data/;

my @array1 = ("part1", "part2", "part3", "part4");
my @array2 = ("part1", "PART2", "part3", "part4");
my @array3 = ("part1", "PART2", "part3", "part4");

# sample usage 
say "1 & 2 are different" if cmp_data(\@array1, \@array2) ;
sat "2 & 3 are the same" unless cmp_data(\@array2, \@array3) ;

你也可以比较哈希值和更复杂的嵌套数据结构(在合理范围内)。对于没有非核心依赖项的单个模块,Data::Cmp相当“智能”;-) ... 呃,我是说“有用的”。


2

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