user1625341
user1625341

Reputation: 89

How can I check if two arrays contain the same elements in Perl?

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

Answers (4)

badp
badp

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

cHao
cHao

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

user554546
user554546

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

David W.
David W.

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:

  • I could have dereferences, joined, generated the checksum, and did the comparison in a single statement. I did them separately in order to make it clearer what I was doing. Programmatically, it probably doesn't make any difference. Perl will optimize the whole thing anyway. I always go for clarity.
  • 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.
  • Be sure to test your parameters. I used the ref function to do this.
  • I cheated. I first turned the two sorted arrays into scalars. Then, I used the sha1 checksum to verify that they're the same. A checksum using the sha1 function should be pretty good. It is highly unlikely to fail.

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

Related Questions