【问题标题】:Unintentionally adding keys to hash while iterating迭代时无意中将键添加到哈希
【发布时间】:2023-03-18 09:03:01
【问题描述】:

我正在遍历指向经度/城市的键/值对的纬度键哈希值的缓存。我正在尝试找到与已经查找的内容足够接近并且在散列中的纬度/经度的近似匹配。

我就是这样做的

    foreach my $lat_key ( keys $lookup_cache_latlonhash ) {

        if ( ($lat > ($lat_key - .5)) && ($lat < ($lat_key + .5)) ) {

            foreach my $lon_key ( keys %{ $lookup_cache_latlonhash->{$lat_key}} ) {

                if ( ($lon > ($lon_key - .5)) && ($lon < ($lon_key + .5)) ) {

                    $country = $$lookup_cache_latlonhash{$lat_key}{$lon_key};
                    print "Approx match found: $lat_key $lon_key $country\n";
                    return $country;
                }
            }
        }
    }

代码用于在该范围内查找这些纬度/经度对。然而,对于它循环使用的每个纬度,当它确实发现它在范围内(第一个嵌套条件)时,它会将其添加到不打算使用的哈希(可能是keys %{ $goog_lookup_cache_latlonhash-&gt;{$lat_key}}),向哈希添加无用/空键:

$VAR1 = {
      '37.59' => {},
      '37.84' => {},
      '37.86' => {},
      '37.42' => {
                   '126.44' => 'South Korea/Jung-gu'
                 },
      '37.92' => {},
      '37.81' => {},
      '38.06' => {
                   '-122.53' => 'America/Novato'
                 },
      '37.8' => {},
      '37.99' => {},
      '37.61' => {},
       ...

进行此查找的聪明或至少理智的方法是什么?所以我不会只是通过查找它们而无意中将键添加到哈希中?

【问题讨论】:

  • 请注意,允许keys 处理引用被认为是一个坏主意,因此已从 Perl v5.24 中删除。所以keys $lookup_cache_latlonhash应该写成keys %$lookup_cache_latlonhash

标签: perl hashtable key-value autovivification


【解决方案1】:

您遇到的是auto-vivification。 Perl 的一个特性是让使用嵌套结构更容易一些。

任何时候取消引用未定义的值,perl 都会自动创建您正在访问的对象。

use Data::Dumper; 
my $hash = {}; if ($hash->{'a'}) {} #No auto-vivification because you're just checking the value   
keys %{$hash->{'b'}}; #auto-vivification because you're acting on the value (getting the keys of it) $hash->{b} 
print Dumper($hash);

有几种方法可以避免这种情况 -

  1. 在你想避免这种情况的范围内添加no autovivification 功能性
  2. 检查您访问的项目是否已定义 或存在(并且是您需要的类型)

我推荐第二个,因为它有助于养成检查代码是否正确数据结构的习惯,并使调试更加容易。

foreach my $lat_key (keys $lookup_cache_latlonhash) {
    if (($lat > ($lat_key - .5)) 
        && ($lat < ($lat_key + .5)) 
        && ref($lookup_cache_latlonhash->{$lat_key}) eq 'HASH')  #expecting a hash here - undefined or any non-hash value will skip the foreach
    {
        foreach my $lon_key (keys %{ $lookup_cache_latlonhash->{$lat_key}}) {
            if (($lon > ($lon_key - .5)) && ($lon < ($lon_key + .5))) {
                $country = $$lookup_cache_latlonhash{$lat_key}{$lon_key};
                print "Approx match found: $lat_key $lon_key $country\n";
                return $country;
            }
        }
    }
}

【讨论】:

  • 非常有意义,并且每个 foreach 之间的夹层是明确的 - 彻底的答案。我想知道为什么 no autovivification 不被 use strict 调用 - 感觉应该是,不是吗?
  • @ikebukuru - 根据我的经验,自动激活是 90% 以上的期望行为(或至少不是不期望的行为)。很多人第一次遇到它时确实会感到惊讶,但除此之外它造成任何伤害的情况极为罕见。
  • 我不清楚为什么你认为keys %{ $lookup_cache_latlonhash-&gt;{$lat_key} } 会在循环for my $lat_key ( keys $lookup_cache_latlonhash ) 内自动激活一个元素,所以该元素必须已经存在。
  • @Borodin 确实!这就是为什么它让我摸不着头脑。感谢您清理我的问题代码以提高可读性。
  • @ikebukuru:我可以看到使用您自己的代码发生这种情况的唯一方法是,如果您的元素已经存在但值为undef,它也将被自动激活。那可能吗?如果不是,那么问题必须存在于您的代码中的其他地方。检查外部 for 循环前后的哈希值。
【解决方案2】:

no autovivification;

在范围内。

【讨论】:

    【解决方案3】:

    您可以为此使用exists keyword

    解决方案

    use Data::Dumper;
    $hash = {};
    $hash{'alpha'} = 'yep';
    $hash{'beta'} = 'this too';
    if (exists $hash{'gamma'}) {
        print "Found gamma."
    }
    print Dumper(\%hash);
    $hash{'gamma'} = 'added';
    if (exists $hash{'gamma'}) {
        print "Gamma was updated.\n"
    }
    print Dumper(\%hash);
    

    示例输出

    $VAR1 = {
              'beta' => 'this too',
              'alpha' => 'yep'
            };
    Gamma was updated.
    $VAR1 = {
              'gamma' => 'added',
              'beta' => 'this too',
              'alpha' => 'yep'
            };
    

    【讨论】:

    • 这与问题中的代码完全无关。
    猜你喜欢
    • 1970-01-01
    • 2016-04-07
    • 2013-09-27
    • 1970-01-01
    • 1970-01-01
    • 2017-09-04
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多