在迭代时无意中向哈希表中添加键

4
我正在遍历一个哈希表的缓存,它是由纬度键指向经度/城市键值对的哈希表。我试图寻找与已查找并在哈希表中的纬度/经度接近的近似匹配项。
我是这样做的:
    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->{$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' => {},
       ...

有什么聪明或至少是理智的方法来进行这种查找吗?这样我就不会在查找时无意中向哈希表中添加键了。


1
请注意,允许keys在引用上工作被认为是一个坏主意,并且已从Perl v5.24中删除。因此,keys $lookup_cache_latlonhash应该写成keys %$lookup_cache_latlonhash - Borodin
3个回答

9
你正在经历的是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;
            }
        }
    }
}

这个回答非常清晰,循环中间的if语句也很明确。我想知道为什么在使用use strict时没有调用no autovivification - 感觉应该调用才对,不是吗? - ikebukuru
2
根据我的经验,自动创建数据结构是期望的行为(或者至少不是不期望的),在绝大多数情况下都是如此。第一次遇到它时确实会让很多人感到惊讶,但除此之外,它极少会造成任何伤害。 - Dave Sherohman
我不清楚您为什么认为keys %{ $lookup_cache_latlonhash->{$lat_key} }会在循环for my $lat_key ( keys $lookup_cache_latlonhash )中自动创建一个元素,因为该元素必须已经存在。 - Borodin
@ikebukuru:我能想到的唯一可能是你的代码中已经存在元素,但值为undef,这也会自动创建。这种情况可能吗?如果不是,那么问题必须存在于代码的其他地方。在外部for循环之前和之后都要检查哈希表。 - Borodin
@TrentonTrama,这是一个自动实现问题;在提问之前,我已经输入了 my $size = keys $lookup_cache_latlonhash; 并打印了 Hash 的 Dumper,并发现它在每个循环中都会增长。在实施您建议的更清洁的解决方案之前,发现使用 no autovivification 指令可以解决这个问题。谢谢! - ikebukuru
显示剩余3条评论

2
你可以使用 exists 关键字 实现这个功能。

解决方案

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

这与问题中的代码完全无关。 - Borodin

2
放置
no autovivification;

在范围内。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接