В Perl есть встроенный способ сравнить два массива для равенства?

у меня есть два массива строк, которые я хотел бы сравнить для равенства:

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

есть ли встроенный способ сравнения массивов, как для скаляров? Я попробовал:

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

но он просто оценивал каждый массив в скалярном контексте и поэтому сравнивал длину каждого массива.

Я могу свернуть свою собственную функцию, чтобы сделать это, но это кажется такой низкоуровневой операцией, что должен быть встроенный способ сделать это. Есть?

Edit: к сожалению, я не имеют доступа к 5.10+ или дополнительным компонентам.

13 ответов


новая 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 раз, и он показывает:

             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;
              Rate array_comp   iterator
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;
              Rate   iterator array_comp
iterator   10014/s         --       -23%
array_comp 13071/s        31%         --

Я не смотрел на использование памяти.


здесь Test:: Moreфункция is_deeply (), которая также будет отображать точно, где структуры отличаются, или Test:: Deep ' s eq_deeply (), который не требует тестовой проводки (и просто возвращает true или false).


Не встроенный, но есть Array:: Compare.

Это одна из операций, которая остается вне ядра Perl по тем, что я считаю дидактическими причинами , то есть, если вы пытаетесь это сделать, вероятно, что-то не так. Наиболее показательным примером этого, я думаю, является отсутствие ядра read_entire_file функция; в основном, предоставление этой функции в ядре приведет к тому, что люди подумают, что это хорошая идея для этого, но вместо этого Perl разработанный таким образом, что мягко подталкивает вас к обработке файлов line-at-a-time, что, как правило, гораздо более эффективно и в остальном лучшая идея, но начинающие программисты редко комфортно с ним, и им нужно некоторое поощрение, чтобы добраться туда.

то же самое относится и здесь: вероятно, есть гораздо лучший способ сделать определение, которое вы пытаетесь выполнить, сравнивая два массива. Не обязательно, но вероятно. Так Perl подталкивает вас думать о других способы достижения цели.


Perl 5.10 дает вам оператор smart match.

use 5.010;

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

в противном случае, как вы сказали, у вас будет свой собственный Top roll.


пока вы используете perl 5.10 или новее, вы можете использовать smart match operator.

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

более простое решение быстрее:

#!/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%         --

для проверки равенства двух массивов попробовать это. В данном коде, если %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 };

этот вопрос превратился в очень полезный ресурс. ++ для критериев и обсуждения.

как указывали другие, функция smart match имела проблемы и постепенно исчезает в своем нынешнем виде. Есть альтернативы, которые "менее умны" (и поэтому избегают проблем), и которые небольшие, довольно быстрые и не имеют слишком много неосновных зависимостей.

вы можете найти ссылки на некоторые довольно хорошие дискуссии об истории будущего ~~ глядя на пару блога @brian d foy, и почтовый архив p5p темы от и 2012 от @rjbs.

сравнение массивов может быть простым и забавным!

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 может сделать тонко настроенное сравнение проще.


Если кожух единственная разница, то вы можете просто использовать:

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

, тогда как "@array1" возвращает то же самое как join ( " ", @array1 )


если порядок и повторяющиеся значения не имеют значения, а только равенство значений (т. е. сравнение наборов), вы можете использовать Set::Scalar.

это перегружает общие операторы, такие как == или !=.

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

if ( Set::Scalar->new(@array1) == Set::Scalar->new(@array2) ) {...}

в качестве альтернативы, есть также Algorithm::Diff и List::Compare.


Data::Cmp еще один недавний вариант. The 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 я имею в виду "полезное".


можно использовать функцию grep в скалярном контексте (http://perldoc.perl.org/functions/grep.html#grep-BLOCK-LIST)

(0 eq (grep { $array1 [ $ _ ] ne $array2 [ $ _ ]} 0..$#array1)) если $#array1 eq $#array2;

взысканное.


Если единственным критерием является " эквивалентны ли они или нет?и не более сложный вопрос: "эквивалентны они или нет, и если они отличаются, то как?"есть гораздо более быстрые / уродливые способы сделать это. Например, разбейте весь массив на два скаляра и сравните их.

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
}