如何从一个数组中减去另一个数组?

20

当我尝试以下操作时

#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;

my @bl = qw(red green blue);
my @a = qw(green yellow purple blue pink);

print Dumper [grep {not @bl} @a];

我得到了一个空数组。我本来期望@a减去@bl后,输出结果为yellow purple pink

这里出了什么问题?


2
“subtract” 不是这里的正确词语。当你找到正确的词语时,你会发现它会触发一次巴甫洛夫哈希攻击。 - tchrist
7个回答

38
你需要将@bl转换为哈希表以执行集合差异操作:
my %in_bl = map {$_ => 1} @bl;
my @diff  = grep {not $in_bl{$_}} @a;

7
这个回答比常见问题解答更好,因为常见问题解答只告诉你如何计算两个数组的“对称差”。 - mob
7
在 Perl 5.10 或更新版本中,您可以这样写:my @diff = grep{ not $_ ~~ @bl } @a; 这将返回在数组 @a 中但不在数组 @bl 中的元素集合 @diff。 - Brad Gilbert
@BradGilbert,6年后你会收到一个警告,提示它是一个实验性功能。该比较可能会在未来发生变化。 - carandraug
@BradGilbert 文档说明智能匹配将$ _@bl中的每个元素匹配。此答案只需迭代一次@bl即可创建哈希表,因此性能差异显着。 http://perldoc.perl.org/perlop.html#Smartmatch-Operator - Floegipoky

4
@b1 评估为 true(它是一个具有非零元素数量的数组),因此您 grep 构造中的布尔测试 (not @b1) 将始终返回 false。grep 过滤一个数组,仅返回布尔测试返回 true 的元素。
您需要测试看看当前考虑的数组元素 $_ 是否在 @bl 中。一种方法是使用 @bl 作为键生成临时哈希表,然后在您的 grep 语句中检查是否存在于哈希表键中的 $_
#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper;

my @bl = qw(red green blue);
my @a = qw(green yellow purple blue pink);

# create a hash
my %h;

# nifty trick - use a hash slice to populate the
# hash. The values are irrelevant so we'll use @bl
# for those too
@h{@bl} = @bl;

print Dumper [grep {!exists $h{$_}} @a];

1
填充 %h 的值有些过度了。如果使用 exists,用 @h{@bl}=() 填充将会很好,并且可能更快。 - Stefan Majewsky

4
自从Perl 5.18.0版本以来,智能匹配运算符被视为实验性的: 智能匹配功能现在是实验性的。 因此,我不会再使用下面的解决方案。 另一种方法是使用Smartmatch运算符(如果您的perl版本大于等于5.010):
#!/usr/bin/env perl
use warnings;
use 5.012;

my @bl = qw(red green blue);
my @a = qw(green yellow purple blue pink);

my @s = grep{ not $_ ~~ @bl } @a;
say "@s"; # yellow purple pink

这样做不会导致O(n^2)的性能问题吗?因为智能匹配运算符会针对@a中的每个元素匹配@bl中的每个元素。 - Floegipoky
@Floegipoky:你的评论对我来说听起来很有道理。在 Perl 5 中,我停止使用 smartmatch 操作符。我已编辑了答案。 - sid_com

4
请参考perlfaq4: How do I compute the difference of two arrays?
在您的代码中,not 可能不是您想要的结果。
如果 @bl 是一个空数组,则 not @bl 总是为 1,如果 @bl 不为空,则为 undef。它并不以任何方式表示“不在 @bl 中的元素”。

2

使用perl5i的另一种选项:

use perl5i::2;

my @bl = qw(red green blue);
my @a = qw(green yellow purple blue pink);
my @diff = @a->diff(\@bl);

say @diff->mo->perl;

1

另一种方法是使用Acme::Tools CPAN 模块中的 minus 函数:

use strict;
use warnings;
use Data::Dumper;
use Acme::Tools qw(minus);

my @bl = qw(red green blue);
my @a  = qw(green yellow purple blue pink);
my @diff = minus(\@a, \@bl);
print Dumper(\@diff);

__END__

$VAR1 = [
          'yellow',
          'purple',
          'pink'
        ];

-1

另一种方法是使用:

List::Compare CPAN module
use List::Compare ;
...
my $compare_obj 
  = List::Compare->new(\@a , \@b1) ;
@diff = $compare_obj->get_Lonly() ;
...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接