那天和人讨论一个简单的应用:集合减法,即给定集合 A 和 B ,计算 C = A - B, 也就是说求属于A但不属于B的元素。集合A和B使用数组来存储,元素仅限于整数。 求计算方法。

Perl自身没有集合运算函数,为了这一个功能也犯不上去安装CPAN模块, 所以我们考虑了以下几个方案:

  1. 利用foreach作最直白的循环来判断
  2. 利用grep代替foreach
  3. 将元素用逗号连接之后用正则表达式匹配
  4. 将数组转化成散列之后判断

四种方法都能实现,但效率如何?本以为使用foreach应该是最快的, 没想到测试之后发现散列居然是最快的。

              Rate    use grep  use regexp use foreach    use hash
use grep     359/s          --        -58%        -63%        -90%
use regexp   861/s        140%          --        -11%        -77%
use foreach  965/s        169%         12%          --        -74%
use hash    3769/s        950%        338%        291%          --

如果元素不是整数而是随机的字符串,正则表达式的效率会急剧下降,而散列依然能保证领先的地位。 看来Perl的散列算法不可小觑啊。

附源代码如下:

use Benchmark qw(:all);

@a =  (1..100);
@b = (10..200);

sub use_grep {
  my ($a, $b) = @_;
  my @result;
  
  @result = grep {
    my $tmp = $_; 
    !grep{$_ eq $tmp} @$b;
  } @$a;
  
  return \@result;
}
  
sub use_foreach {
  my ($a, $b) = @_;
  my @result;
  
  foreach my $x (@$a){
    my $not_exist = 1;
    foreach my $y (@$b){
      if($x eq $y){
        $not_exist = 0;
        last;
      }
    }
    push @result, $x if ($not_exist);
  }
  
  return \@result;
}

sub use_regexp {
  my ($a, $b) = @_;
  my @result;

  my $str = ',' . (join ',', @$b) . ',';
  foreach (@$a) {
    push @result, $_ unless $str =~ ",$_,";
  }

  return \@result;
}


sub use_hash {
  my ($a, $b) = @_;
  my @result;
  my %hash;

  foreach (@$b) { $hash{$_} = 1; }
  foreach (@$a) { push @result, $_ unless $hash{$_}; }

  return \@result;
}

#print "use grep: " . (join ',', @{&use_grep(\@a, \@b)}) . "\n";
#print "use foreach: " . (join ',', @{&use_foreach(\@a, \@b)}) . "\n";
#print "use regexp: " . (join ',', @{&use_regexp(\@a, \@b)}) . "\n";
#print "use hash: " . (join ',', @{&use_hash(\@a, \@b)}) . "\n";

#__END__
cmpthese(3000, {
    'use grep' => sub { &use_grep(\@a, \@b) },
    'use foreach' => sub { &use_foreach(\@a, \@b) },
    'use regexp' => sub { &use_regexp(\@a, \@b) },
    'use hash' => sub { &use_hash(\@a, \@b) },
});