In Perl, is there a built in way to compare two arrays for equality?

ArraysPerlCompareMatch

Arrays Problem Overview


I have two arrays of strings that I would like to compare for equality:

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

Is there a built-in way to compare arrays like there is for scalars? I tried:

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

but it just evaluated each array in scalar context, and so compared the length of each array.

I can roll my own function to do it, but it seems like such a low-level operation that there should be a built-in way to do it. Is there?

Edit: sadly, I don't have access to 5.10+ or optional components.

Arrays Solutions


Solution 1 - Arrays

There is the new 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;

Regarding Array::Compare:

> Internally the comparator compares the two arrays by using join to turn both arrays into strings and comparing the strings using eq.

I guess that is a valid method, but so long as we are using string comparisons, I would much rather use something like:

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

If the arrays you are comparing are large, joining them is going to do a lot of work and consume a lot of memory than just comparing each element one by one.

Update: Of course, one should test such statements. Simple benchmarks:

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

This is the worst case scenario where elementwise_eq has to go through each and every element in both arrays 1_000 times and it shows:

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

On the other hand, the best case scenario is:

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 performance drops quite quickly, however:

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

I did not look at memory utilization.

Solution 2 - Arrays

There's Test::More's is_deeply() function, which will also display exactly where the structures differ, or Test::Deep's eq_deeply(), which doesn't require a test harness (and just returns true or false).

Solution 3 - Arrays

Not built-in, but there is Array::Compare.

This is one of the operations that's left out of the Perl core for what I believe are didactic reasons -- that is, if you're trying to do it, there's probably something wrong. The most illustrative example of this, I think, is the absence of a core read_entire_file function; basically, providing that function in the core would lead people to think it's a good idea to do that, but instead, Perl is designed in a way that gently nudges you toward processing files line-at-a-time, which is generally far more efficient and otherwise a better idea, but novice programmers are rarely comfortable with it and they need some encouragement to get there.

The same applies here: there is probably a much better way to make the determination you're trying to accomplish by comparing two arrays. Not necessarily, but probably. So Perl is nudging you to think about other ways of accomplishing your goal.

Solution 4 - Arrays

Perl 5.10 gives you the smart match operator.

use 5.010;

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

Otherwise, as you said, you'll have top roll your own.

Solution 5 - Arrays

So long as you are using perl 5.10 or newer, you can use the smart match operator.

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

Solution 6 - Arrays

Simpler solution is faster:

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

And result in 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%         --

Solution 7 - Arrays

This question has turned into a very useful resource. ++ for the benchmarks and discussion.

As others have pointed out smart match feature had issues and is being phased out in its current form. There are alternatives that are "less smart" (and so avoid the issues) and that are small, fairly fast and don't have too many non CORE dependencies.

You can find links to some pretty good discussions about the history of the future of ~~ by looking at a couple of blog posts by @brian d foy, and the p5p mail archive threads from 2011 and 2012 from @rjbs.

Comparing arrays can be simple and fun!

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

... especially fun if the array is simple. But an array can be a complicated thing, and sometimes you want different kinds of information from the results of the comparison. For that, Array::Compare can make fine tuned comparison easier.

Solution 8 - Arrays

Data::Cmp is another recent option. The cmp_data() function operates similarly to the cmp operator (see perlop for cmp usage).

Example:

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) ;

It's also possible to compare hashes and more complicated nested data structures (within reason). For a single module with no non-core dependencies, Data::Cmp is pretty "smart" ;-) ... errm I mean "useful".

Solution 9 - Arrays

If casing is the only difference, you can simply use:

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

Whereas "@array1" returns the same as join ( " ", @array1 )

Solution 10 - Arrays

If order and duplicate values do not matter but only values equality (i.e. set comparison), you could use Set::Scalar.

It overloads common operators such as == or !=.

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

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

Alternatively, there's also Algorithm::Diff and List::Compare.

Solution 11 - Arrays

For checking equality of two arrays try this. In given code, if %eq_or_not has any value then both arrays are not equal otherwise they are equal.

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 };

Solution 12 - Arrays

My core-only solution with List::Util::all:

use List::Util qw(all);

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

As a subroutine:

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

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

If you want a custom comparison:

# 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;
}

At this point, all doesn't save much space and you could just do a 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;
}

Solution 13 - Arrays

One could use grep function in scalar context (http://perldoc.perl.org/functions/grep.html#grep-BLOCK-LIST)

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

HIH.

Solution 14 - Arrays

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

if performance concern can be ignored, as mentioned several times in the threads here

Solution 15 - Arrays

If the only criteria is "are they equivalent or not?", and not the more complex question, "are they equivalent or not, and if they differ, how?" there are much quicker/uglier ways to do it. For example, smash the entirety of each array into two scalars and compare those.

For example

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
}

Yes, I probably just made Larry Wall cry.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBillView Question on Stackoverflow
Solution 1 - ArraysSinan ÜnürView Answer on Stackoverflow
Solution 2 - ArraysEtherView Answer on Stackoverflow
Solution 3 - ArrayschaosView Answer on Stackoverflow
Solution 4 - ArraysDavid HarrisView Answer on Stackoverflow
Solution 5 - ArraysQuentinView Answer on Stackoverflow
Solution 6 - ArraysHynek -Pichi- VychodilView Answer on Stackoverflow
Solution 7 - ArraysG. CitoView Answer on Stackoverflow
Solution 8 - ArraysG. CitoView Answer on Stackoverflow
Solution 9 - ArraystempireView Answer on Stackoverflow
Solution 10 - ArraysArcView Answer on Stackoverflow
Solution 11 - ArraysPradeep GuptaView Answer on Stackoverflow
Solution 12 - ArrayssnipsnipsnipView Answer on Stackoverflow
Solution 13 - Arraysf_vView Answer on Stackoverflow
Solution 14 - ArraysSteve S.View Answer on Stackoverflow
Solution 15 - ArraysAlwaysLearningView Answer on Stackoverflow