Perl 检查数组散列的散列时的混淆

Perl 检查数组散列的散列时的混淆,perl,data-structures,hash,hash-of-hashes,Perl,Data Structures,Hash,Hash Of Hashes,我试图将我的散列输入与数据结构中允许的有效选项进行比较,如果它不是选项之一,那么我将为键设置默认值。不过,我似乎遗漏了一些东西 当前数据结构的示例 my $opts = { file => { require => 1 }, head => { default => 1, allowed => [0,1], }, type => {

我试图将我的散列输入与数据结构中允许的有效选项进行比较,如果它不是选项之一,那么我将为键设置默认值。不过,我似乎遗漏了一些东西

当前数据结构的示例

my $opts = {
     file => { require => 1 },
     head => {
               default => 1,
               allowed => [0,1],
             },
     type => { 
               default => 'foo', 
               allowed => [qw(foo bar baz)] 
             },
};
$args
是我的hash ref
(file=>'file.txt',type=>'foo',head=>1)

我试过的一小段

for my $k ( keys %$opts ) {
   croak("Argument '$k' is required in constructor call!")
       if $opts->{$k}->{require} and !exists $args->{$k};

   if (exists $args->{$k}) {
     if (grep {!$args->{$k}} @{$opts->{$k}->{allowed}} ) {
       $args->{$k} = $opts->{$k}->{default};
     }
     ...
   } else {
     ..set our defaults
     $args->{$k} = $opts->{$k}->{default};
   }
}

检查允许的值时出错

grep
函数接受一个代码块和一个列表。它会依次为列表中的每个元素设置
$\uu
变量。如果块返回真值,则保留该元素。在标量上下文中,
grep
不返回保留元素的列表,而是返回计数

您的
grep
块是
{!$args->{$k}
。当
$args->{$k}
为false时,返回true,反之亦然。结果不依赖于
$\uuu
,因此不会检查参数是否为允许的值之一

要查看给定值是否为允许值,您必须测试某种形式的等价性,例如

if (grep { $args->{$k} eq $_ } @{ $opts->{$k}{allowed} }) {
  # this is executed when the arg matches an allowed value
} else {
  # the arg is not allowed
}
智能匹配和列表::MoreUtils之旅 如果您可以使用perl>v10,则可以使用智能匹配。这将把上述条件表示为

use 5.010;

$args->{$k} ~~ $opts->{$k}{allowed}
声明,如果arg是标量(字符串/数字),那么这大致相当于
grep
,并且允许的arrayref也只包含普通标量

然而,在v18中,智能匹配被重新标记为experimantal,行为可能很快就会改变

同时,最好还是坚持使用显式
grep
等,但我们可以实现两个改进:

  • grep
    将测试所有元素,即使已经找到匹配项。这可能是低效的。
    List::Util
    核心模块中的
    first
    函数的语法与
    grep
    相同,但在第一个元素之后停止。如果块与某个值匹配,则返回该值。如果没有匹配的值,则返回
    undef
    。当
    undef
    可能是一个有效值时,甚至当允许使用假值时,这会使事情变得复杂。但是在您的情况下,
    grep
    可以替换为

    use List::Util 'first';
    
    defined first { $_ eq $args->{$k} } @{ $opts->{$k}{allowed} }
    
    MoreUtils模块具有更多的功能。例如,它提供了
    any
    函数,该函数对应于数学表达式∃ (存在)量词:

    use List::MoreUtils 'any';
    
    any { $_ eq $args->{$k} } @{ $opts->{$k}{allowed} }
    
    这只返回一个布尔值。虽然它可能没有普通的
    grep
    first
    那么有效,但使用
    any
    是非常自我记录的,并且更易于使用

  • 到目前为止,我一直假设我们只会对允许的值进行字符串比较。这有时是可行的,但最好指定一个显式模式。比如说

    croak qq(Value for "$k": "$args->{$k}" not allowed) unless
         $opts->{$k}{mode} eq 'str'   and any { $args->{$k} eq $_ } @{ $opts->{$k}{allowed} }
      or $opts->{$k}{mode} eq 'like'  and any { $args->{$k} =~ $_ } @{ $opts->{$k}{allowed} }
      or $opts->{$k}{mode} eq 'num'   and any { $args->{$k} == $_ } @{ $opts->{$k}{allowed} }
      or $opts->{$k}{mode} eq 'smart' and any { $args->{$k} ~~ $_ } @{ $opts->{$k}{allowed} }
      or $opts->{$k}{mode} eq 'code'  and any { $args->{$k}->($_) } @{ $opts->{$k}{allowed} };
    
  • 防止未知选项 您可能希望也可能不希望禁止
    $args
    哈希中的未知选项。特别是如果考虑类的可组合性,则可能希望忽略未知选项,因为超类或子类可能需要这些选项。

    但如果您选择检查错误的选项,则可以
    删除那些您已经处理过的元素:

    my $self = {};
    for my $k (keys %$opts) {
      my $v = delete $args->{$k};
      ...; # use $v in the rest of the loop
      $self->{$k} = $v;
    }
    
    croak "Unknown arguments (" . (join ", ", keys %$args) . ") are forbidden" if keys %$args;
    
    grep
    用于未知参数:

    my @unknown = grep { not exists $opts->{$_} } keys %$args;
    croak "Unknown arguments (" . (join ", ", @unknown) . ") are forbidden" if @unknown;
    
    for my $k (keys %$opts) {
      ...;
    }
    
    或者您可以循环使用
    $args
    $opts
    的组合键:

    use List::Util 'uniq';
    
    for my $k (uniq keys(%$opts), keys(%$args)) {
      croak "Unknown argument $k" unless exists $opts->{$k};
      ...;
    }
    
    标量上下文 我假设您正确地初始化了
    $args
    作为散列引用:

    my $args = { file => 'file.txt', type => 'foo', head => 1 };
    
    使用parens而不是curlies在语法上是有效的:

    my $args = ( file => 'file.txt', type => 'foo', head => 1 );
    
    但这不会产生散列。相反,
    =>
    的行为类似于C中的逗号运算符:计算并丢弃左操作数。也就是说,只保留最后一个元素:

    my $args = 1; # equivalent to above snippet.
    

    @JasonGray我查看了你的更新代码。在循环中,您应该区分arg是否存在:
    if(exists$args->{$k}{test\u allowed}else{assign\u default}
    。当测试允许的值时,您应该首先检查是否有允许的值列表,否则您要接受所有值:
    if(my$allowed=$opts->{$k}->{allowed}){croak“Value…notallowed!”,除非有任何{$args->{$k}eq${}@$allowed}
    。这样可以避免硬编码检查。