【问题标题】:In Perl, is there a built in way to compare two arrays for equality?在 Perl 中,是否有一种内置方法来比较两个数组是否相等?
【发布时间】:2021-05-07 21:59:33
【问题描述】:

我有两个字符串数组,我想比较它们是否相等:

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

有没有像标量那样比较数组的内置方法? 我试过了:

if (@array1 == @array2) {...}

但它只是在标量上下文中评估每个数组,因此比较每个数组的长度。

我可以滚动自己的函数来执行此操作,但它似乎是一个低级操作,应该有一个内置的方法来执行它。有吗?

编辑:遗憾的是,我无法访问 5.10+ 或可选组件。

【问题讨论】:

    标签: arrays perl compare match


    【解决方案1】:

    有新的smart match operator

    #!/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 次,它显示:

    速率迭代器 array_comp 迭代器 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% 迭代器 52600/s 5622% --

    iterator 的性能下降很快,但是:

    my @x = 1 .. 20, map { rand } 1 .. 1_000;
    my @y = 1 .. 20, map { rand } 1 .. 1_000;
    
    速率迭代器 array_comp 迭代器 10014/s -- -23% array_comp 13071/s 31% --

    我没有查看内存利用率。

    【讨论】:

    • @Bill 谢谢你,但分析不是那么彻底。它只是教我不要在没有测量的情况下做出假设,而且在大多数情况下,你可以做得比Array::Compare 更糟。
    • 简单的my $i;for my $e (@$xref) {return unless $e eq $yref->[$i++];} 要快得多(v5.14.2)。
    • 智能运算符在 2013 年 5 月发布的 Perl 5.18 版中已弃用。
    • @Flimm 有一些“不太聪明”的智能匹配替代方案仍然可以进行“全面”匹配。到目前为止,我最喜欢的一个是match::simple
    【解决方案2】:

    Test::More 的 is_deeply() 函数也可以准确显示结构不同的地方,或者 Test::Deep 的 eq_deeply(),它不需要测试工具(只返回 true 或 false) .

    【讨论】:

    • 你不能 use 用于测试的模块......除非在测试中。
    【解决方案3】:

    不是内置的,但是有Array::Compare

    这是 Perl 核心中遗漏的操作之一,我认为这是出于教学原因 - 也就是说,如果您尝试这样做,则可能有问题。我认为最能说明这一点的例子是缺少核心 read_entire_file 函数;基本上,在核心中提供该功能会导致人们认为这样做是一个好主意,但是相反,Perl 的设计方式是温和地推动您处理文件的一行 -时间,这通常效率更高,而且是一个更好的主意,但新手程序员很少能适应它,他们需要一些鼓励才能到达那里。

    这同样适用于:通过比较两个数组来做出您想要完成的决定可能有更好的方法。不是必要,但可能。因此,Perl 正在推动您考虑其他实现目标的方法。

    【讨论】:

    • Perl 作为一种表达力如此之强的语言,在让您思考自己在做什么方面做得很好。
    • 那么在核心中提供dump 函数是否意味着随机死亡并留下核心转储是个好主意?
    • @chaos - 这是一个有趣的观点。我正在尝试将我在 Xml 层次结构中的位置与从文本文件中获得的数组进行匹配。 (遗憾的是,没有 XPath 模块。)
    • 我不喜欢Array::Compare,因为它的工作方式:它将元素与"\x{07}" 作为分隔符连接在一起,并比较生成的字符串。您可以指定不同的分隔符,但如果您不能保证特定字符不会出现在数组的输入中,则这是错误的。
    【解决方案4】:

    Perl 5.10 为您提供了智能匹配运算符。

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

    否则,正如您所说,您将拥有自己的顶级滚动。

    【讨论】:

    • 实际上,这不会告诉您数组是否相同,但它们是否可比较
    • 更糟糕的是,这取决于您使用的是 5.10.0 还是 5.10.1+。在 5.10.0 上确实会检查数组是否相等。
    • 我真的不喜欢用“可比”这个词来描述数组智能匹配的行为。我自动将其读作“能够被比较”(这将是相当无用的行为)而不是“等效”(恕我直言,这将是一个更好的术语)。
    • 智能运算符在 2013 年 5 月发布的 Perl 5.18 版中已弃用。
    【解决方案5】:

    只要您使用 perl 5.10 或更新版本,您就可以使用smart match operator

    if (@array1 ~~ @array2) {...}
    

    【讨论】:

    • 实际上,这不会告诉您数组是否相同,但它们是否可比较
    • 请参阅“智能匹配详细信息”(perldoc.perl.org/perlsyn.html#Smart-matching-in-detail),了解有关“可比”含义的更多详细信息。 通常它的意思是“是的,它们基本上是一样的”。
    • 智能运算符在 2013 年 5 月发布的 Perl 5.18 版中已弃用。
    【解决方案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;
    }
    

    结果为@​​987654322@:

                 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%         --
    

    【讨论】:

      【解决方案7】:

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

      正如其他人指出的那样,智能匹配功能存在问题,并且正在以目前的形式逐步淘汰。有些替代品“不太聪明”(因此可以避免这些问题),而且体积小、速度相当快并且没有太多的非 CORE 依赖项。

      您可以通过查看@brian d foy 的几个blog postsp5p 邮件存档 线程,找到一些关于~~ 未来历史的很好讨论的链接来自@rjbs 的from 20112012

      比较数组既简单又有趣!

      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 for: " ..." :-) 请参阅 p5p 帖子了解上下文。
      • match::simple 无法比较数组。对于my @x = (1, 2, 3)my @y = (4, 5, 6)@x |M| @y 为真,因为操作数是在标量上下文中求值的。只要项目数相等,它们就匹配。至于数组引用,the doc 说:“如果右侧是数组引用,则运算符递归到数组中,如果左侧匹配任何数组元素,则匹配成功。”也不是我们需要的。
      • match::smart 正确处理(递归)。
      • @x-yuri 谢谢,固定的例子。
      【解决方案8】:

      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 非常“聪明”;-) ... errm 我的意思是“有用”。

      【讨论】:

        【解决方案9】:

        如果大小写是唯一的区别,您可以简单地使用:

        if (lc "@array1" eq lc "@array2") {...}
        

        "@array1" 返回的结果与join ( " ", @array1 ) 相同

        【讨论】:

        • 在这种情况下,大小写很重要。那么if ("@array1" eq "@array2") {...} 会将数组折叠成一个字符串然后比较结果吗?出于好奇,("a", "b") 与 ("a", "b") 相比如何?
        • 如果大小写很重要,请忽略 lc。 ("a", "b") 将不同于 ("a", "b"),因为比较将每个数组视为一个字符串。
        • 由于控制信息如何放入数组中隐含在数组的存在中,我一直认为这是最简单的解决方案。
        • 您可能想先对这些数组进行排序,是吗?
        • 它会发现qw(a b)等于qw(Ab)
        【解决方案10】:

        如果顺序和重复值无关紧要,而只是值相等(即设置比较),您可以使用Set::Scalar

        它重载了常见的运算符,例如==!=

        my @array1 = ("part1", "part2", "part3", "part4");
        my @array2 = ("part1", "PART2", "part3", "part4");
        
        if ( Set::Scalar->new(@array1) == Set::Scalar->new(@array2) ) {...}
        

        另外,还有Algorithm::DiffList::Compare

        【讨论】:

          【解决方案11】:

          为了检查两个数组的相等性,试试这个。 在给定的代码中,如果 %eq_or_not 有任何值,则两个数组不相等,否则它们相等。

          my @array1 = ("part1", "part2", "part3", "part4");
          my @array2 = ("part1", "PART2", "part3", "part4");
          
          my %eq_or_not;
          
          @eq_or_not{ @array1 } = undef;
          delete @eq_or_not{ @array2 };
          

          【讨论】:

          • 如果订单和潜在的重复不相关,这很方便。给予试探性的赞成票,因为这恰好符合我当前的用例。但实际上使用这种方法,我们比较的是集合,而不是数组。
          • 第一眼看起来很棒,但它没有正确回答问题。尝试比较qw(a b)qw(a b c)
          • 还比较qw(A A B)qw(A B)
          【解决方案12】:

          我使用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;
          }
          

          【讨论】:

            【解决方案13】:

            可以在标量上下文中使用 grep 函数 (http://perldoc.perl.org/functions/grep.html#grep-BLOCK-LIST)

            (0 eq (grep { $array1[ $_ ] ne $array2[ $_ ] } 0..$#array1)) if $#array1 eq $#array2;

            喂。

            【讨论】:

              【解决方案14】:

              if (join(",",sort @a) eq join(",",sort @b))

              如果性能问题可以忽略,正如这里的线程中多次提到的那样

              【讨论】:

                【解决方案15】:

                如果唯一的标准是“它们是否相等?”,而不是更复杂的问题,“它们是否相等,如果它们不同,如​​何区分?”有更快/更丑的方法来做到这一点。例如,将每个数组的整体分解为两个标量并进行比较。

                例如

                my @array1 = ("part1", "part2", "part3", "part4");
                my @array2 = ("part1", "PART2", "part3", "part4");
                
                my $smash1 = join("", @array1);
                my $smash2 = join("", @array2);
                
                if ($smash1 eq $smash2)
                {
                  # equal
                }
                else
                {
                  #unequal
                }
                

                是的,我可能只是让拉里·沃尔哭了。

                【讨论】:

                • 你会发现qw(foo bar)qw(foobar)相等。
                • ++ @Hynek-Pichi-Vychodil -- 请参阅我的回复:perlancar 的 Data::Cmp 模块。 _cmp_data() 函数似乎是比较数据结构的一种适当详尽的方法。事后看来,Smart Match 之类的东西过于雄心勃勃了。
                • @G.Cito:是的,它很详尽,尤其是它耗尽了 CPU 资源。
                猜你喜欢
                • 2013-07-26
                • 1970-01-01
                • 2011-05-08
                • 1970-01-01
                • 1970-01-01
                • 2013-08-18
                • 2010-09-07
                • 2017-09-22
                • 2021-09-14
                相关资源
                最近更新 更多