Есть ли в Perl встроенный способ сравнения двух массивов на равенство? - PullRequest
50 голосов
/ 22 октября 2009

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

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

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

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

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

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

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

Ответы [ 13 ]

56 голосов
/ 22 октября 2009

Появился новый оператор smart match :

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

Относительно Массив :: Сравнить :

Внутренне компаратор сравнивает два массива, используя объединение, чтобы превратить оба массива в строки, и сравнивая строки, используя 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%         --

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

22 голосов
/ 22 октября 2009

Есть Test :: More * Функция is_deeply () , которая также будет отображать точное различие структур, или Test :: Deep eq_deeply (), которая не не требует тестового жгута (и просто возвращает true или false).

14 голосов
/ 22 октября 2009

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

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

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

9 голосов
/ 22 октября 2009

Perl 5.10 предоставляет вам умный оператор сопоставления.

use 5.010;

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

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

8 голосов
/ 22 октября 2009

Пока вы используете Perl 5.10 или новее, вы можете использовать оператор умного совпадения .

if (@array1 ~~ @array2) {...}
6 голосов
/ 01 декабря 2011

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

#!/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%         --
2 голосов
/ 16 декабря 2014

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

Как уже отмечали другие, у функции умного совпадения возникли проблемы, и она в настоящее время постепенно сокращается. Существуют альтернативы, которые являются «менее умными» (и поэтому избегают проблем) и которые являются небольшими, достаточно быстрыми и не имеют слишком много зависимостей, не соответствующих CORE.

Вы можете найти ссылки на некоторые довольно хорошие обсуждения истории будущего ~~, посмотрев пару сообщений в блоге @brian d foy и почтовый архив p5p потоков от 2011 и 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 может упростить сравнение.

1 голос
/ 16 августа 2018

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 довольно «умный» ; -) ... э-э, я имею в виду «полезный».

1 голос
/ 01 марта 2017

Для проверки равенства двух массивов попробуйте это. В данном коде, если% 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 };
1 голос
/ 17 июня 2016

Если порядок и повторяющиеся значения не имеют значения, а только равенство значений (т. Е. Сравнение множеств), вы можете использовать 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.

...