【问题标题】:How can I find out if elements of an array match any elements of another array?如何确定数组的元素是否与另一个数组的任何元素匹配?
【发布时间】:2012-06-12 04:44:26
【问题描述】:

如果我有哈希

my %foo = ( foo => 1, bar => 1 );

我想检查 %foo 的任何键是否在比较数组中(显然键 %foo 只是一个数组)。我一直在想一些不存在的奇怪语法。

my @cmp0 = qw( foo baz    );
my @cmp1 = qw( baz blargh );

if keys %foo in @cmp0 # returns true because key foo is in the array
if keys %foo in @cmp1 # returns false because no key in foo is an element of cmp1

最简单的方法是什么?

【问题讨论】:

标签: perl


【解决方案1】:

List::MoreUtils 有一个名为any 的函数,它使用类似于grep 的语法,但在第一次满足条件时停止其内部循环。这种行为的优点是需要的迭代次数要少得多(假设交叉点的随机分布)。

any 的另一个优点是代码清晰:它以其功能命名。 Perl 最佳实践不鼓励在布尔上下文中使用grep,因为grep 的假定用途是返回匹配的元素列表。它在布尔上下文中工作,但对于读者来说,代码的意图不如 any 清楚,后者专为布尔使用而设计。

any 确实添加了对 List::MoreUtils 的依赖。但是,List::MoreUtils 是无处不在的模块之一,它很可能已经安装了。

这是一个例子:

use List::MoreUtils qw( any );

my %foo = ( foo => 1, bar => 1 );

my @cmp0 = qw( foo baz    );
my @cmp1 = qw( baz blargh );

print "\@cmp0 and %foo have an intersection.\n" 
    if any { exists $foo{$_} } @cmp0;

print "\@cmp1 and %foo have an intersection.\n"
    if any { exists $foo{$_} } @cmp1;

另一个选项是~~Smart Match Operator,它在 Perl 5.10.0 和更新版本中可用。可以这样使用:

my %foo = ( foo => 1, bar => 1 );

my @cmp0 = qw( foo baz    );
my @cmp1 = qw( baz blargh );

print "\@cmp0 and %foo have an intersection.\n" if @cmp0 ~~ %foo;

print "\@cmp1 and %foo have an intersection.\n" if @cmp1 ~~ %foo;

使用 smartmatch,您消除了 List::MoreUtils 依赖,取而代之的是最小的 Perl 版本依赖。代码是否像any一样清晰,由你决定。

【讨论】:

  • +1 表示无需查找即可知道智能匹配运算符的作用。
  • Perl6::Junction 也有一个有用的 any() 函数。
  • 虽然List::MoreUtils::any 不需要完全迭代,但我的性能基准测试表明它在我的机器上比grep 慢10 倍,即使使用XS 也是如此。 List::Util::first 稍微快一点,但仍然没有grep 快。
  • 这在很大程度上取决于列表的大小以及交叉点的分布。想象一个包含 10,000 个元素的列表,在第 5,000 个位置有一个交叉点。 grep 将消耗 10,000 次迭代,any 将消耗 5,000 次。在 PerlMonks 上进行冥想可能是一个有趣的讨论,鼓励进行更深入的讨论。
  • 10k 大小的列表?如果我正在处理一个大小的列表,我希望我会使用 SQL 之类的东西来处理它......我只是尽量不写 assembler 在一些向后兼容代码中的 10、20 个选项.
【解决方案2】:

最简洁的编写方法是使用grepexists 运算符。

这段代码

my %foo = ( foo => 1, bar => 1 );

my @cmp0 = qw( foo baz    );
my @cmp1 = qw( baz blargh );

print "YES 0\n" if grep { exists $foo{$_} } @cmp0;
print "YES 1\n" if grep { exists $foo{$_} } @cmp1;

给出这个输出

YES 0

【讨论】:

    【解决方案3】:

    grep 是个好主意,而且可能是最干净的。但是,您也可以使用logical OR assignment operator ||=:

    my $found;
    $found ||= exists $foo{$_} for @cmp1;
    

    【讨论】:

      【解决方案4】:

      这些是简单的设置操作

      use strictures;
      use Set::Scalar qw();
      ⋮
      my $foo = Set::Scalar->new(keys %foo);
      $foo->intersection(Set::Scalar->new(@cmp0))->size; # 1
      $foo->intersection(Set::Scalar->new(@cmp1))->size; # 0
      

      【讨论】:

        【解决方案5】:

        像往常一样,有更多方法可以解决这个问题。你可以这样做:

        #!/usr/bin/perl
        
        use strict ;
        use warnings ;
        
        my %hash = ( foo => 1 , bar => 1 ) ;
        my %cmp = ( cmp0 => [ qw(foo baz) ] ,
                    cmp1 => [ qw(baz blargh) ] ) ;
        
        my @hash_keys = keys %hash ;
        foreach my $compare ( keys %cmp ) {
          my %tmp ;
          # Generate a temporary hash from comparison keys via hash slice
          @tmp{@{$cmp{$compare}}} = undef ;
          INNER:
          foreach my $hash_key ( @hash_keys ) {
            if( exists $tmp{$hash_key} ) {
              printf "Key '%s' is part of '%s'.\n" , $hash_key , $compare ;
              last INNER ;
            }
          }
        }
        

        这给出了:

        Key 'foo' is part of 'cmp0'.
        

        【讨论】:

        • 这是“真正的程序员可以用任何语言编写汇编代码!”的一个例子吗?
        • 不,这是“累了?去睡觉,不要回答这样的问题”的例子。
        猜你喜欢
        • 1970-01-01
        • 2017-02-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多