使用Perl的两个数组的差异

时间:2010-05-29 01:02:16

标签: perl arrays

我有两个数组。我需要检查并查看其中一个元素是否出现在另一个元素中。

有没有比嵌套循环更有效的方法呢?我每个都有几千个元素,需要经常运行程序。

10 个答案:

答案 0 :(得分:37)

另一种方法是使用Array::Utils

use Array::Utils qw(:all);

my @a = qw( a b c d );
my @b = qw( c d e f );

# symmetric difference
my @diff = array_diff(@a, @b);

# intersection
my @isect = intersect(@a, @b);

# unique union
my @unique = unique(@a, @b);

# check if arrays contain same members
if ( !array_diff(@a, @b) ) {
        # do something
}

# get items from array @a that are not in array @b
my @minus = array_minus( @a, @b );

答案 1 :(得分:25)

perlfaq4救援:

  

如何计算两个数组的差异?如何计算两个数组的交集?

     

使用哈希。这是两个以上的代码。它假定每个元素在给定数组中是唯一的:

   @union = @intersection = @difference = ();
    %count = ();
    foreach $element (@array1, @array2) { $count{$element}++ }
    foreach $element (keys %count) {
            push @union, $element;
            push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element;
    }

如果正确声明了变量,代码看起来更像是:

my %count;
for my $element (@array1, @array2) { $count{$element}++ }

my ( @union, @intersection, @difference );
for my $element (keys %count) {
    push @union, $element;
    push @{ $count{$element} > 1 ? \@intersection : \@difference }, $element;
}

答案 2 :(得分:11)

您需要提供更多背景信息。有更有效的方法可以做到:

  • 走出Perl并使用shell(sort + comm

  • map将一个数组转换为Perl哈希,然后循环检索另一个检查哈希成员资格。这具有线性复杂度(“M + N” - 基本上在每个数组上循环一次),而不是具有“M * N”复杂度的嵌套循环

    示例:

    my %second = map {$_=>1} @second;
    my @only_in_first = grep { !$second{$_} } @first; 
    # use a foreach loop with `last` instead of "grep" 
    # if you only want yes/no answer instead of full list
    
  • 使用为您做最后一个项目符号点的Perl模块(注释中提到了List :: Compare)

  • 如果卷非常大并且您需要经常重新比较,则根据添加元素的时间戳来执行此操作。几千个元素还不够大,但我最近不得不区分100k大小的列表。

答案 3 :(得分:7)

你可以试试Arrays::Utils,它会让它看起来既美观又简单,但它并没有在后端做任何强大的魔法。这是array_diffs代码:

sub array_diff(\@\@) {
    my %e = map { $_ => undef } @{$_[1]};
    return @{[ ( grep { (exists $e{$_}) ? ( delete $e{$_} ) : ( 1 ) } @{ $_[0] } ), keys %e ] };
}

由于Arrays::Utils不是标准模块,因此您需要问自己是否值得安装和维护此模块。否则,它非常接近DVK的答案。

您必须注意某些事项,并且必须在特定情况下定义您想要做的事情。让我们说:

@array1 = qw(1 1 2 2 3 3 4 4 5 5);
@array2 = qw(1 2 3 4 5);

这些数组是否相同?或者,他们是不同的?它们具有相同的值,但@array1而不是@array2中存在重复值。

这个怎么样?

@array1 = qw( 1 1 2 3 4 5 );
@array2 = qw( 1 1 2 3 4 5 );

我会说这些数组是相同的,但Array::Utils::arrays_diff要求不同。这是因为Array::Utils假设没有重复的条目。

而且,即使mob指出的Perl FAQ也说它假设每个元素在给定数组中都是唯一的。这是你可以做出的假设吗?

无论如何,哈希就是答案。查找哈希很容易,也很快。问题是你想用独特的价值做什么。

这是一个可靠的解决方案,假设重复无关紧要:

sub array_diff {
    my @array1 = @{ shift() };
    my @array2 = @{ shift() }; 

    my %array1_hash;
    my %array2_hash;

    # Create a hash entry for each element in @array1
    for my $element ( @array1 ) {
       $array1_hash{$element} = @array1;
    }

    # Same for @array2: This time, use map instead of a loop
    map { $array_2{$_} = 1 } @array2;

    for my $entry ( @array2 ) {
        if ( not $array1_hash{$entry} ) {
            return 1;  #Entry in @array2 but not @array1: Differ
        }
    }
    if ( keys %array_hash1 != keys %array_hash2 ) {
       return 1;   #Arrays differ
    }
    else {
       return 0;   #Arrays contain the same elements
    }
}

如果重复确实重要,您需要一种方法来计算它们。这里使用map不仅可以创建由数组中每个元素键入的哈希值,还可以计算数组中的重复项:

my %array1_hash;
my %array2_hash;
map { $array1_hash{$_} += 1 } @array1;
map { $array2_hash{$_} += 2 } @array2;

现在,您可以浏览每个哈希并验证密钥不仅存在,而且条目匹配

for my $key ( keys %array1_hash ) {
    if ( not exists $array2_hash{$key} 
       or $array1_hash{$key} != $array2_hash{$key} ) {
       return 1;   #Arrays differ
    }
 }

如果%array1_hash中的所有条目都与%array2_hash中的相应条目匹配,则您只会退出for循环。现在,您必须显示%array2_hash中的所有条目也与%array1_hash中的条目匹配,并且%array2_hash没有更多条目。幸运的是,我们可以做我们以前做过的事情:

if ( keys %array2_hash != keys %array1_hash ) {
     return 1;  #Arrays have a different number of keys: Don't match
}
else {
     return;    #Arrays have the same keys: They do match
}

答案 4 :(得分:2)

您可以使用它来获取两个数组之间的差异

#!/usr/bin/perl -w
use strict;

my @list1 = (1, 2, 3, 4, 5);
my @list2 = (2, 3, 4);

my %diff;

@diff{ @list1 } = undef;
delete @diff{ @list2 };

答案 5 :(得分:1)

n + n log n算法,如果确定元素在每个数组中都是唯一的(作为散列键)

my %count = (); 
foreach my $element (@array1, @array2) { 
    $count{$element}++;
}
my @difference = grep { $count{$_} == 1 } keys %count;
my @intersect  = grep { $count{$_} == 2 } keys %count;
my @union      = keys %count;

所以,如果我不确定统一性并希望检查array2中array1元素的存在,

my %count = (); 
foreach (@array1) {
    $count{$_} = 1 ;
};
foreach (@array2) {
    $count{$_} = 2 if $count{$_};
};
# N log N
if (grep { $_ == 1 } values %count) {
    return 'Some element of array1 does not appears in array2'
} else {
    return 'All elements of array1 are in array2'.
} 
# N + N log N

答案 6 :(得分:1)

my @a = (1,2,3); 
my @b=(2,3,1); 
print "Equal" if grep { $_ ~~ @b } @a == @b;

答案 7 :(得分:0)

尝试使用List:Compare。 IT拥有可在阵列上执行的所有操作的解决方案。 https://metacpan.org/pod/List::Compare

答案 8 :(得分:0)

你想要将@x的每个元素与@y中相同索引的元素进行比较,对吗?这样就可以了。

print "Index: $_ => \@x: $x[$_], \@y: $y[$_]\n" 
    for grep { $x[$_] != $y[$_] } 0 .. $#x;

...或...

foreach( 0 .. $#x ) {
    print "Index: $_ => \@x: $x[$_], \@y: $y[$_]\n" if $x[$_] != $y[$_];
}

您选择哪种类型取决于您是否更有兴趣保留不同元素的索引列表,或者只是对逐个处理不匹配感兴趣。 grep版本可以方便地获取不匹配列表。 (original post

答案 9 :(得分:0)

不优雅,但易于理解:

#!/usr/local/bin/perl 
use strict;
my $file1 = shift or die("need file1");
my $file2 = shift or die("need file2");;
my @file1lines = split/\n/,`cat $file1`;
my @file2lines = split/\n/,`cat $file2`;
my %lines;
foreach my $file1line(@file1lines){
    $lines{$file1line}+=1;
}
foreach my $file2line(@file2lines){
    $lines{$file2line}+=2;
}
while(my($key,$value)=each%lines){
    if($value == 1){
        print "$key is in only $file1\n";
    }elsif($value == 2){
        print "$key is in only $file2\n";
    }elsif($value == 3){
        print "$key is in both $file1 and $file2\n";
    }
}
exit;
__END__