55

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.

G. Cito
  • 6,210
  • 3
  • 29
  • 42
Bill
  • 14,257
  • 4
  • 43
  • 55

15 Answers15

57

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.

mivk
  • 13,452
  • 5
  • 76
  • 69
Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
  • 2
    @Bill thank you, but the analysis is not that thorough. It does the bare minimum to teach me not to make assumptions without measurement and that in, most cases, you could do worse than `Array::Compare`. – Sinan Ünür Oct 23 '09 at 14:22
  • 2
    Simple `my $i;for my $e (@$xref) {return unless $e eq $yref->[$i++];}` is far faster (v5.14.2). – Hynek -Pichi- Vychodil Dec 01 '11 at 01:41
  • 13
    Smart operator is deprecated in the Perl release 5.18, release May 2013. – Flimm Jul 01 '14 at 16:53
  • 1
    @Flimm There are a few "less smart" alternatives to smart match which still do "comprehensive" matching. One of my favorites so far is [`match::simple`](https://metacpan.org/pod/match::simple). – G. Cito Dec 16 '14 at 17:41
26

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

Ether
  • 53,118
  • 13
  • 86
  • 159
13

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.

chaos
  • 122,029
  • 33
  • 303
  • 309
  • 2
    For being such an expressive language, Perl does a great job of making you think about what you are doing. – Jamison Dance Oct 22 '09 at 19:44
  • 1
    So does providing the function `dump` in core mean it's a good idea to randomly die and leave a core dump sitting around? – jrockway Oct 23 '09 at 07:38
  • @chaos - that's an interesting point. I'm attempting to match my location in an Xml hierarchy against an array obtained from a text file. (No XPath modules, sadly.) – Bill Oct 23 '09 at 13:06
  • I don't like `Array::Compare`, because of the way it works: it joins elements together with `"\x{07}"` as a separator, and compares the resulting strings. You can specify a different separator, but this is buggy if you cannot guarantee that a specific character won't appear in the array's input. – Flimm Jul 01 '14 at 16:56
8

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.

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
David Harris
  • 2,332
  • 1
  • 13
  • 25
  • 4
    Actually, that will not tell you if the arrays are the same but if they are *comparable*. – Sinan Ünür Oct 22 '09 at 19:46
  • 3
    Worse, it depends whether you use 5.10.0 or 5.10.1+. On 5.10.0 that does indeed check that the arrays are equal. – hobbs Oct 22 '09 at 19:59
  • 6
    I really dislike using the term "comparable" to describe the behavior of smart matching on arrays. I automatically read it as "able to be compared" (which would be rather useless behavior) rather than as "equivalent" (which would be a much better term, IMHO). – Michael Carman Oct 22 '09 at 23:32
  • 2
    Smart operator is deprecated in the Perl release 5.18, release May 2013. – Flimm Jul 01 '14 at 16:54
7

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

if (@array1 ~~ @array2) {...}
Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • 4
    Actually, that will not tell you if the arrays are the same but if they are *comparable*. – Sinan Ünür Oct 22 '09 at 19:49
  • See "Smart Matching in Detail" (http://perldoc.perl.org/perlsyn.html#Smart-matching-in-detail) For more details on what "Comparable" means. *Usually* it means "Yeah, they're basically the same". – Robert P Oct 22 '09 at 23:53
  • 2
    Smart operator is deprecated in the Perl release 5.18, release May 2013. – Flimm Jul 01 '14 at 16:54
6

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%         --
Hynek -Pichi- Vychodil
  • 26,174
  • 5
  • 52
  • 73
3

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.

G. Cito
  • 6,210
  • 3
  • 29
  • 42
  • best smart match joke has to go to @rjbs for: " ..." :-) See the p5p posts for context. – G. Cito Dec 16 '14 at 17:33
  • 1
    `match::simple` can't compare arrays. With `my @x = (1, 2, 3)` and `my @y = (4, 5, 6)`, `@x |M| @y` is true, since the operands get evaluated in scalar context. As long as number of items is equal, they match. As for array refs, [the doc](https://metacpan.org/pod/match::simple) says: "If the right hand side is an arrayref, then the operator recurses into the array, with the match succeeding if the left hand side matches any array element." Not what we need either. – x-yuri Mar 19 '18 at 10:25
  • 1
    `match::smart` handles that properly (recursively). – x-yuri Mar 19 '18 at 10:35
  • @x-yuri Thanks, fixed example. – G. Cito Mar 22 '18 at 04:46
3

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

Edit: List::Util::first works as a substitute on older perls (< v5.20).

use List::Util qw(first);

if (@array1 == @array2 && !defined first { $array1[$_] ne $array2[$_] } 0..$#array1) {
    print "matched\n";
}
snipsnipsnip
  • 2,268
  • 2
  • 33
  • 34
2

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

($#array1 == $#array2) && (0 == (grep { $array1[ $_ ] ne $array2[ $_ ] } 0..$#array1))
snipsnipsnip
  • 2,268
  • 2
  • 33
  • 34
f_v
  • 33
  • 5
2

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".

G. Cito
  • 6,210
  • 3
  • 29
  • 42
0

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

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

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

Tempire
  • 2,290
  • 19
  • 14
  • 2
    Case is important in this case. So would `if ("@array1" eq "@array2") {...}` collapse the arrays into a string and then compare the result? Out of curiosity, how would ("a ", "b") compare to ("a", " b")? – Bill Nov 25 '09 at 19:38
  • 1
    If case is important, then leave out the lc. ("a", "b") would be different than ("a", " b"), because the comparison is treating each array as a string. – Tempire Nov 25 '09 at 20:49
  • 1
    Since controlling how information is put into an array is implicit in its existence, I've always found this to be the simplest solution. – Tempire Nov 25 '09 at 20:53
  • 1
    You'd probably want to sort those arrays first, yeah? – Duncan Jan 27 '10 at 04:41
  • If you're comparing them for equality, it seems to me sorting would defeat the purpose. – Tempire Jan 28 '10 at 04:00
  • 4
    It will find `qw(a b)` equal to `qw(Ab)`. – Hynek -Pichi- Vychodil Dec 01 '11 at 00:23
  • Indeed, as the question indicated, the comparison needed to be case-insensitive. – Tempire Dec 11 '11 at 18:54
  • @tempire: the question did not indicate that the comparison needed to be case-insensitive. – Flimm Dec 17 '14 at 16:14
  • Upvoting because `if ("@array1" eq "@array2")` is a very handy way to compare, say, arrays of numbers or elements that do not contain spaces. If the elements can contain spaces, it would clearly be not reliable, such as for `("a ", "b")` vs. `("a", " b")`. – Alexander Gelbukh Sep 28 '17 at 14:02
0

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.

Arc
  • 11,143
  • 4
  • 52
  • 75
0

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 };
Pradeep Gupta
  • 535
  • 5
  • 10
  • 1
    This is handy if the order and potential duplicates are irrelevant. Giving a tentative upvote because that happens to match my current use case. But really using this approach we're comparing sets, not arrays. – Philipp Hanes Apr 27 '17 at 17:26
  • Looks great on first glance, but it doesn't correctly answer the question. Try to compare `qw(a b)` and `qw(a b c)`! – Wolf Jun 04 '19 at 10:10
  • Also compare `qw(A A B)` and `qw(A B)`! – Wolf Jun 04 '19 at 10:31
-1

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

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

Steve S.
  • 71
  • 6
-2

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.

AlwaysLearning
  • 796
  • 3
  • 10