Reputation: 89
So all I need is a simple way to tell if two arrays are identical in perl. Order does not matter, so I'm looking for something like this:
my @a = (1, 2, 3);
my @b = (2, 3, 1);
my @c = (1, 2, 4);
&identical(@a, @b)
returns 1
&identical(@a, @c)
returns 0
Thanks!
Upvotes: 8
Views: 7242
Reputation: 11813
I guess you could write it like this in a way that makes the least assumptions on the kind of input you are dealing with (just pass the appropriate comparison sub):
use List::Util;
sub identical {
my @this = @{ +shift };
my @that = @{ +shift };
my $cmp = shift // sub { shift eq shift };
return '' unless @this == @that;
for my $idx (List::Util::shuffle keys @this) {
return '' unless $cmp->($this[$idx], $that[$idx]);
}
return 1;
}
which behaves like so:
0> identical([0..100], [0..100])
$res[0] = 1
1> identical([0..100], ['0.0', 1..100])
$res[1] = ''
2> identical([0..100], ['0.0', 1..100], sub {shift == shift})
$res[2] = 1
3> identical(['0.66666666666', 0..10], [2/3, 0..10], sub {shift == shift})
$res[3] = ''
4> identical(['0.66666666666', 0..10], [2/3, 0..10], sub {shift() - shift() < 1e-5})
$res[4] = 1
# if you need this to be true check out https://stackoverflow.com/a/12127428/13992
5> identical([0..100], [List::Util::shuffle(0..100)])
$res[5] = ''
Upvotes: 0
Reputation: 86506
You could tally the elements' counts in a hash. Have a (element => count) hash, and bump the count up every time the first array has that element, and down every time the other has it (or vice versa). If the two arrays have all the same elements, every value in the hash will be 0.
sub have_same_elements {
my ($arr1, $arr2) = @_;
my %counts = ();
$counts{$_} += 1 foreach (@$arr1);
$counts{$_} -= 1 foreach (@$arr2);
return !(grep { $_ != 0 } values %counts);
}
$a_and_b_same = have_same_elements(\@a, \@b); # will be true
$a_and_c_same = have_same_elements(\@a, \@c); # will be false
(Note, this might or might not work with objects that do their own stringification. Hash keys can't be references, so Perl stringifies references as you use them. Its default stringifier turns references into something like ARRAY(0x12345678)
, which makes references distinct unless they're to the same thingie. But if an object does its own stringification and doesn't return distinct strings for different references, this will probably break. Just so you know.)
Upvotes: 7
Reputation:
If you're using Perl 5.10
or greater (and if you aren't, you really should upgrade), you can use the smart match operator:
use strict;
use warnings;
my @a = (1, 2, 3);
my @b = (2, 3, 1);
my @c = (1, 2, 4);
#sort each of them (numerically)
@a = sort { $a <=> $b } @a;
@b = sort { $a <=> $b } @b;
@c = sort { $a <=> $b } @c;
if ( @a ~~ @b ) {
print "\@a and \@b are the same! (after sorting)\n";
}
else {
print "nope\n";
}
if ( @a ~~ @c ) {
print "\@a and \@c are the same! (after sorting)\n";
}
else {
print "nope\n";
}
You could also roll your own function:
use strict;
use warnings;
my @a = (1, 2, 3);
my @b = (2, 3, 1);
my @c = (1, 2, 4);
print same_elements(\@a, \@b) . "\n";
print same_elements(\@a, \@c) . "\n";
#arguments are two array references
sub same_elements {
my $array_ref_1 = shift;
my $array_ref_2 = shift;
my @arr1 = @$array_ref_1;
my @arr2 = @$array_ref_2;
#If they are not the same length, we are done.
if( scalar(@arr1) != scalar(@arr2) ) {
return 0;
}
#sort them!
@arr1 = sort { $a <=> $b } @arr1;
@arr2 = sort { $a <=> $b } @arr2;
foreach my $i( 0 .. $#arr1 ) {
if ( $arr1[$i] != $arr2[$i] ) {
return 0;
}
}
return 1;
}
Upvotes: 6
Reputation: 107030
First off, you are going to have to rethink your function.
identical(@a, @b);
Doesn't pass two arrays to the function, but passes a single array with all of the elements in both arrays in it. It's as if you said:
identical(1, 2, 3, 2, 3, 1);
In order for your function to work, you'll have to pass references to your arrays:
identical(\@a, \@b);
I'd say to prototype your subroutine, but that's probably going to cause you more problems that it'll solve.
If order is not important, sort the arrays before comparing them. You might even be able to cheat...
sub identical {
my $array_ref_1 = shift;
my $array_fef_2 = shift;
use Digest::SHA qw(sha1_hex);
if ( ref( $array_ref_1 ) ne "ARRAY") or ( ref( $array_ref_2 ) ne "ARRAY") {
return; #Error, need two array references
}
# Dereference Arrays
my @array_1 = @{$array_ref_1};
my @array_2 = @{$array_ref_2};
# Setup Arrays to be one big scalar
my $scalar_1 = join "\n", sort @array_1;
my $scalar_2 = join "\n", sort @array_2;
my $checksum_1 = sha1_hex $scalar_1;
my $checksum_2 = sha1_hex $scalar_2;
if ($checksum_1 eq $checksum_2) {
return 1;
}
else {
return 0_but_true;
A few notes:
0_but_true
returns a 0, but at the same time returns a true value. This way, you can do something like if ( identical( \@A, \@B ) ) {
to make sure that the function worked. Then, you can test for zero or one.The real issue is what if you had multi-lined arrays like this:
@a = ("this", "that", "the\nother");
@b = ("this", "that\nthe", "other");
Using the join
the way I did would cause the resulting scalars to be equal.
Upvotes: 3