【问题标题】:Confusion with checking Hash of Hash of Arrays与检查数组哈希的哈希混淆
【发布时间】:2013-07-20 03:28:08
【问题描述】:

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

当前数据结构示例..

my $opts = {
     file => { require => 1 },
     head => {
               default => 1,
               allowed => [0,1],
             },
     type => { 
               default => 'foo', 
               allowed => [qw(foo bar baz)] 
             },
};

$args 是我的哈希引用( 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};
   }
}

【问题讨论】:

    标签: perl data-structures hash hash-of-hashes


    【解决方案1】:

    检查允许值有问题。

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

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

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

    if (grep { $args->{$k} eq $_ } @{ $opts->{$k}{allowed} }) {
      # this is executed when the arg matches an allowed value
    } else {
      # the arg is not allowed
    }
    

    智能匹配List::MoreUtils

    如果您可以使用 perl > v10,则可以使用智能匹配。这会将上述条件表示为

    use 5.010;
    
    $args->{$k} ~~ $opts->{$k}{allowed}
    

    lengthy table of possible type combinations 指出,如果 arg 是标量(字符串/数字),这大致相当于 grep,并且允许的 arrayref 也只包含普通标量。

    但是,智能匹配在 v18 中被重新标记为实验性,并且行为可能很快就会改变。

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

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

      use List::Util 'first';
      
      defined first { $_ eq $args->{$k} } @{ $opts->{$k}{allowed} }
      

      List::MoreUtils 模块具有更多功能。它提供了例如any 函数,它对应于数学上的∃(存在)量词:

      use List::MoreUtils 'any';
      
      any { $_ eq $args->{$k} } @{ $opts->{$k}{allowed} }
      

      这仅返回一个布尔值。虽然它可能不如普通的 grepfirst 高效,但使用 any 是相当自记录的,并且更易于使用。

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

      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 哈希中禁止未知选项。特别是如果您考虑类的可组合性,您可能希望忽略未知选项,因为超类或子类可能需要这些选项。

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

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

    使用括号代替花括号在语法上是有效的:

    my $args = ( file => 'file.txt', type => 'foo', head => 1 );
    

    但这不会产生哈希。相反,=>, 的行为类似于 C 中的逗号运算符:评估并丢弃左操作数。也就是说,只保留最后一个元素:

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

    【讨论】:

    • @JasonGray 我查看了您更新的代码。在循环中,您应该区分 arg 是否存在:if (exists $args->{$k}) { test_allowed } else { assign_default }。在测试允许值时,您应该首先检查是否存在允许值列表,否则您希望接受所有值:if (my $allowed = $opts->{$k}->{allowed}) { croak "Value … not allowed!" unless any { $args->{$k} eq $_ } @$allowed }。这样可以避免硬编码检查。
    猜你喜欢
    • 2019-03-22
    • 2017-04-28
    • 2018-06-14
    • 2011-05-27
    • 2017-04-13
    • 1970-01-01
    • 1970-01-01
    • 2019-05-12
    • 2017-11-15
    相关资源
    最近更新 更多