views:

877

answers:

6

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.

+12  A: 

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
For being such an expressive language, Perl does a great job of making you think about what you are doing.
Jergason
Insightful, complete and concise answer.
iddqd
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
@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
+5  A: 

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

if (@array1 ~~ @array2) {...}
David Dorward
Actually, that will not tell you if the arrays are the same but if they are *comparable*.
Sinan Ünür
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
+3  A: 

Perl 5.10 gives you the 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.

David Harris
Actually, that will not tell you if the arrays are the same but if they are *comparable*.
Sinan Ünür
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
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
+12  A: 

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.

Sinan Ünür
A very thorough analysis, thank you!
Bill
@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
+7  A: 

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
A: 

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

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

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

Glen
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
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.
Glen
Since controlling how information is put into an array is implicit in its existence, I've always found this to be the simplest solution.
Glen
@Glen: You'd probably want to sort those arrays first, yeah?
Duncan
If you're comparing them for equality, it seems to me sorting would defeat the purpose.
Glen