How to compare 2 arrays and split in 3 groups?

2020-04-21 05:11发布

问题:

Let's say I have these arrays

my @new = qw/a b c d e/;
my @old = qw/a b   d e f/;

and I would like to them compared, so I get 3 new arrays containing the differences

  • an array with the elements that are in @new and not in @old : c
  • an array with the elements that are not in @new and in @old : f
  • an array with the elements that are in both @new and @old : a b d e

I am thinking about the exists function, but that only works for hashes I suppose.

Update: I messed up the letter examples.

回答1:

Here's a function that I've used many, many times.

sub compute_sets {
    my ($ra, $rb) = @_;
    my (@a, @b, @ab, %a, %b, %seen);

    @a{@$ra} = ();
    @b{@$rb} = ();

    foreach (keys %a, keys %b) {
        next if $seen{$_}++;

        if (exists $a{$_} && exists $b{$_}) {
            push(@ab, $_);
        }
        elsif (exists $a{$_}) {
            push(@a, $_);
        }
        else {
            push(@b, $_);
        }
    }

    return(\@a, \@b, \@ab);
}

It returns references to arrays containing the elements in the first/second/both lists:

my @new = qw/a b c d e/;
my @old = qw/a b   d e f/;

my ($new_only, $old_only, $both) = compute_sets(\@new, \@old);

say 'new only: ', join ' ', @$new_only; # c
say 'old only: ', join ' ', @$old_only; # f
say 'both: ', join ' ', @$both;         # e a b d


回答2:

See How do I compute the difference of two arrays? How do I compute the intersection of two arrays? in the Perl FAQ.



回答3:

UPDATE2: As Michael Carman points out, my algorithm will fail if elements repeat. So a fixed solution uses one more hash:

my (%count, %old);
$count{$_} = 1 for @new;
$old{$_}++ or $count{$_}-- for @old;
# %count is now really like diff(1)    

my (@minus, @plus, @intersection);
foreach (keys %count) {
    push @minus, $_        if $count{$_}  < 0;
    push @plus, $_         if $count{$_}  > 0;
    push @intersection, $_ if $count{$_} == 0;
};

UPDATE: Looks like this solution also covers what's in the FAQ:

    push @difference, $_ if $count{$_};
    push @union, $_;


回答4:

See also Algorithm::Diff



回答5:

List::Compare handles this type of problem.

#!/usr/bin/perl
use strict;
use warnings;
use List::Compare;

my @new = qw/a b c d e/;
my @old = qw/a b   d e f/;

my $lc = List::Compare->new(\@new, \@old);

# an array with the elements that are in @new and not in @old : c
my @Lonly = $lc->get_Lonly;
print "\@Lonly: @Lonly\n";

# an array with the elements that are not in @new and in @old : f
my @Ronly = $lc->get_Ronly;
print "\@Ronly: @Ronly\n";

# an array with the elements that are in both @new and @old : a b d e
my @intersection = $lc->get_intersection;
print "\@intersection: @intersection\n";

__END__
** prints

@Lonly: c
@Ronly: f
@intersection: a b d e


回答6:

How about:

#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;

my @new = qw/a b c d e/;
my @old = qw/a b   d e f/;
my %new = map{$_ => 1} @new;
my %old = map{$_ => 1} @old;

my (@new_not_old, @old_not_new, @new_and_old);
foreach my $key(@new) {
    if (exists $old{$key}) {
        push @new_and_old, $key;
    } else {
        push @new_not_old, $key;
    }
}
foreach my $key(@old) {
    if (!exists $new{$key}) {
        push @old_not_new, $key;
    }
}

print Dumper\@new_and_old;
print Dumper\@new_not_old;
print Dumper\@old_not_new;

output:

$VAR1 = [
          'a',
          'b',
          'd',
          'e'
        ];
$VAR1 = [
          'c'
        ];
$VAR1 = [
          'f'
        ];


标签: arrays perl