通过数组列表访问/修改Perl中的深层哈希

3

我想删除哈希表中第一个键为$key[0],第二个键为$key[1]等,直到@key结束的元素(无论深度如何)。

例如,如果@key=(23,56,78),则我想操作$hash{23}{56}{78}
我事先不知道@key有多少个元素。

我一直在尝试使用以下内容:

my %the_path;
my $temp=\%the_path;
for(my $cline=0;$cline<=$#keys;$cline++){
     my $cfolder=$keys[$cline];
     $temp->{$cfolder}={};
     $temp=$temp->{$cfolder};
}

但是,我不确定如何操作这里的元素。我该怎么做呢?


1
标量 @key 给你长度,不是吗? - Jerome
听起来像是作业。你尝试过自己解决这个问题吗? - Philip Potter
这是关于编程的内容,请将其从英语翻译成中文。请仅返回已翻译的文本:这不是作业,我已经尝试过了。 - john-jones
1
你尝试了什么?也许我们可以展示你哪里出错了。 - Philip Potter
你在谈论哈希,但是提到了数组($key[0])?不确定你的意思。你能发一下你尝试过的样例吗? - zigdon
显示剩余2条评论
3个回答

5

Data::Diver 就是专门为此目的而存在的。

my $last_hash = Data::Diver::Dive( \%hash, @keys[0..$#keys-1] );
if ($last_hash) { delete $last_hash->{ $keys[-1] } }

你的意思不是应该这样吗: my $hash_ref=%hash; my $last_hash = Data::Diver::Dive( $hash_ref, @keys[0..$#keys-1] ); if ($last_hash) { delete $last_hash->{ $keys[-1] }; } - john-jones

1

您需要遍历哈希引用树,使用列表中的下一个键值作为遍历的键,直到到达列表中倒数第二个键;然后删除与最后一个键相关联的哈希引用值。

my $hash_ptr = $my_hash_ref;
foreach my $key_num (0..$#keys) {
    my $key = $keys[$key_num];
    if (exists $hash_ptr->{$key}) {
        if ($key_num == $#keys) {
            delete $hash_ptr->{$key};
        } else {
            $hash_ptr = $hash_ptr->{$key}; # Go down 1 level
        }
    } else {
        last;
    }
}

注意:这不会删除哈希引用树中最后一个节点上面的任何元素,即使它们不再包含键。换句话说,它只删除一个节点,而不是整个路径 - 它不会像Evan所说的那样修剪树。如果这不是您的意思,请澄清。

刚刚重新测试了代码 - 它可以正常工作。不确定为什么会被踩。 - DVK
你的程序有一个错误;对于具有键a、b、c的{a=>{c=>42}},不应该进行删除操作(但我没有投反对票)。 - ysth
如果您在循环之后执行删除操作,例如简化版代码:my $hp = $root || {}; $hp = $hp->{$_} || {} for @keys[-@keys..-2]; delete $hp->{ $keys[-1] };将会更简单。 - ysth

1

这里有一个递归的例子:

use strict;
use warnings;

my $hash = { foo => { here => 'there', bar => { baz => 100 } } };

## mutates input
sub delete_hash {
  my ( $hash, $keys ) = @_;
  my $key = shift @$keys;
  die "Stopped recursing $key doesn't exist"
    unless exists $hash->{$key}
  ;
  scalar @$keys
    ? delete_hash( $hash->{$key}, $keys )
    : delete $hash->{$key}
  ;
}

delete_hash( $hash, [qw/foo bar/] );

use XXX;
YYY $hash;

堆栈会增长,函数调用也有代价。你可以使用 Perl 的 TCO 版本来缓解这个问题,代码如下:

if (scalar @$keys) {
  @_=($hash->{$key}, $keys);
  goto &delete_hash;
}
else {
  delete $hash->{$key}
}

另外,需要注意的是,这段代码没有修剪树:如果你删除了[qw/foo bar baz/],那么bar将成为空哈希引用。

foo:
  bar: {}
  here: there

如果你有性能方面的顾虑,当然可以将递归展开成循环(这样你就得到了我的答案 :))。“修剪”这个词用得好 - 我会借用它,抱歉 :) - DVK
你可以根据你的目标随时消除子调用。也许我会写一个带有goto变体的基准测试。 - Evan Carroll

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