使用Perl的两个数组的差异

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

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

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

  use Array::Utils qw(:all); my @a = qw( abcd ); my @b = qw( cdef ); # 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 ); 

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; } 

你需要提供更多的上下文。 有更有效的方法来做到这一点,范围从:

  • 去Perl之外,使用shell( sort + comm

  • map一个数组map到Perl哈希,然后循环检查哈希成员。 这具有线性复杂性(“M + N” – 基本上遍历每个arrays一次),而不是具有“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在注释中提到)

  • 如果音量非常大,则根据添加元素的时间戳进行操作,而且需要经常重新比较。 几千个元素不够大,但是我最近不得不区分10万个大小的列表。

你可以试试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); 

这些arrays是一样的吗? 或者,他们有什么不同? 它们具有相同的值,但@array1有重复项,而不是@array2

那这个呢?

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

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

而且,即使是暴徒指出的Perl常见问题还说, 它假设每个元素在给定的数组中是唯一的 。 这是你可以做的一个假设吗?

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

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

 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不仅仅是为了创build一个由数组中的每个元素键入的散列,还要计算数组中的重复:

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

现在,你可以通过每一个散列,并validation不仅有钥匙存在,但他们的条目匹配

 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中的所有条目与%array1_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 } 

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

 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; 

所以,如果我不确定统一性,并想检查数组2中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 
 my @a = (1,2,3); my @b=(2,3,1); print "Equal" if grep { $_ ~~ @b } @a == @b; 

尝试使用列表:比较。 IT可以解决所有可以在arrays上执行的操作。 https://metacpan.org/pod/List::Compare

你想@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[$_]; } 

你select哪种types取决于你是否对不同的元素保持一个索引列表感兴趣,或者只是对逐个处理不匹配感兴趣。 grep版本很方便获取不匹配的列表。 ( 原文 )

你可以用它来获得两个数组之间的差异

 #!/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 }; 

不优雅,但容易理解:

 #!/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__