开发者

How can I compare arrays in Perl?

开发者 https://www.devze.com 2022-12-25 17:58 出处:网络
I have two arrays, @a and @开发者_开发问答b. I want to do a compare among the elements of the two arrays.

I have two arrays, @a and @开发者_开发问答b. I want to do a compare among the elements of the two arrays.

my @a = qw"abc def efg ghy klm ghn";
my @b = qw"def ghy jgk lom com klm";

If any element matches then set a flag. Is there any simple way to do this?


First of all, your 2 arrays need to be written correctly.

@a = ("abc","def","efg","ghy","klm","ghn");
@b = ("def","efg","ghy","klm","ghn","klm");

Second of all, for arbitrary arrays (e.g. arrays whose elements may be references to other data structures) you can use Data::Compare.

For arrays whose elements are scalar, you can do comparison using List::MoreUtils pairwise BLOCK ARRAY1 ARRAY2, where BLOCK is your comparison subroutine. You can emulate pairwise (if you don't have List::MoreUtils access) via:

if (@a != @b) {
    $equals = 0;
} else {
    $equals = 1;
    foreach (my $i = 0; $i < @a; $i++) {
        # Ideally, check for undef/value comparison here as well 
        if ($a[$i] != $b[$i]) { # use "ne" if elements are strings, not numbers
                                # Or you can use generic sub comparing 2 values
            $equals = 0;
            last;
        }
    }
}

P.S. I am not sure but List::Compare may always sort the lists. I'm not sure if it can do pairwise comparisons.


List::Compare

if ( scalar List::Compare->new(\@a, \@b)->get_intersection ) {
    …
}


Check to create an intersect function, which will return a list of items that are present in both lists. Then your return value is dependent on the number of items in the intersected list.

You can easily find on the web the best implementation of intersect for Perl. I remember looking for it a few years ago.

Here's what I found :


my @array1 = (1, 2, 3);
my @array2 = (2, 3, 4);
my %original = ();
my @isect = ();

map { $original{$_} = 1 } @array1;
@isect = grep { $original{$_} } @array2;


This is one way:

use warnings;
use strict;
my @a = split /,/, "abc,def,efg,ghy,klm,ghn";
my @b = split /,/, "def,ghy,jgk,lom,com,klm";
my $flag = 0;
my %a;
@a{@a} = (1) x @a;
for (@b) {
    if ($a{$_}) {
        $flag = 1;
        last;
    }
}
print "$flag\n";


From the requirement that 'if any element matches', use the intersection of sets:

sub set{
  my %set = map { $_, undef }, @_;
  return sort keys %set;
}
sub compare{
    my ($listA,$listB) = @_;
    return ( (set(@$listA)-set(@$listB)) > 0)
}


my @a = qw' abc def efg ghy klm ghn ';
my @b = qw' def ghy jgk lom com klm ';

my $flag;

foreach  my $item(@a) {
  $flag = @b~~$item ? 0 : 1;
  last if !$flag;
}

Note that you will need Perl 5.10, or later, to use the smart match operator (~~) .


Brute force should do the trick for small a n:

my $flag = 0;
foreach my $i (@a) {
    foreach my $k (@b) {
        if ($i eq $k) {
            $flag = 1;
            last;
        }
    }
}

For a large n, use a hash table:

my $flag   = 0;
my %aa     = ();
   $aa{$_} = 1 foreach (@a);
foreach my $i (@b) {
    if ($aa{$i}) {
        $flag = 1;
        last;
    }
}

Where a large n is |@a| + |@b| > ~1000 items


IMHO, you should use List::MoreUtils::pairwise. However, if for some reason you cannot, then the following sub would return a 1 for every index where the value in the first array compares equal to the value in the second array. You can generalize this method as much as you want and pass your own comparator if you want to, but at that point, just installing List::MoreUtils would be a more productive use of your time.

use strict; use warnings;

my @a = qw(abc def ghi jkl);
my @b = qw(abc dgh dlkfj jkl kjj lkm);
my $map = which_ones_equal(\@a, \@b);

print join(', ', @$map), "\n";

sub which_ones_equal {
    my ($x, $y, $compare) = @_;
    my $last = $#$x > $#$y ? $#$x : $#$y;
    no warnings 'uninitialized';
    return [ map { 0 + ($x->[$_] eq $y->[$_]) } $[ .. $last ];
}


This is Perl. The 'obvious' solution:

my @a = qw"abc def efg ghy klm ghn";
my @b = qw"def ghy jgk lom com klm";
print "arrays equal\n"
    if @a == @b and join("\0", @a) eq join("\0", @b);

given "\0" not being in @a.

But thanks for confirming that there is no other generic solution than rolling your own.


my @a1 = qw|a b c d|;
my @a2 = qw|b c d e|;

for my $i (0..$#a1) {
    say "element $i of array 1 was not found in array 2" 
        unless grep {$_ eq $a1[$i]} @a2
}


If you would consider the arrays with different order to be different, you may use Array::Diff

if (Array::Diff->diff(\@a, \@b)->count) {
   # not_same
} else {
   # same
}


This question still could mean two things where it states "If any element matches then set a flag":

  1. Elements at the same position, i.e $a[2] eq $b[2]
  2. Values at any position, i.e. $a[3] eq $b[5]

For case 1, you might do this:

# iterate over all positions, and compare values at that position
my @matches = grep { $a[$_] eq $b[$_] } 0 .. $#a;

# set flag if there's any match at the same position 
my $flag = 1 if @matches;

For case 2, you might do that:

# make a hash of @a and check if any @b are in there
my %a = map { $_ => 1 } @a;
my @matches = grep { $a{$_} } @b;

# set flag if there's matches at any position 
my $flag = 1 if @matches;

Note that in the first case, @matches holds the indexes of where there are matching elements, and in the second case @matches holds the matching values in the order in which they appear in @b.

0

精彩评论

暂无评论...
验证码 换一张
取 消