Perl:如何将数组转换为嵌套的哈希键

4
我需要将一个扁平的键列表转换为嵌套的哈希表,如下所示:
``` my $hash = {}; my @array = qw(key1 key2 lastKey Value); ToNestedHash($hash, @array); ```
会做出这样的结果:
``` $hash{'key1'}{'key2'}{'lastKey'} = "Value"; ```
4个回答

12
sub to_nested_hash {
    my $ref   = \shift;  
    my $h     = $$ref;
    my $value = pop;
    $ref      = \$$ref->{ $_ } foreach @_;
    $$ref     = $value;
    return $h;
}

解释:

  • 将第一个值作为哈希引用
  • 将最后一个值作为要分配的值
  • 其余的是键。
  • 然后创建一个指向基本哈希表的标量引用。
  • 重复进行以下操作:
    • 取消引用指针以获取哈希(第一次)或自动处理指针为哈希
    • 获取键的哈希插槽
    • 并将标量引用分配给哈希插槽。
    • ( 再下一次循环中,这将自动扩展到指示的哈希 )。
  • 最后,通过对最内部插槽的引用来分配值。

我们知道:

  • 哈希或数组的元素只能是标量或引用。
  • 引用本身是一种标量。 (my $h = {}; my $a = [];).
  • 所以,\$h->{$key} 是指向堆上标量插槽的引用,可能被自动扩展。
  • 如果我们按照这样的方式访问嵌套哈希的“级别”,那么它可以自动扩展为哈希引用。

这可能更加明确:

foreach my $key ( @_ ) { 
    my $lvl = $$ref = {};
    $ref    = \$lvl->{ $key };
}

然而,由于经常使用这些引用习语,我在发布之前完全按照原样编写并测试了该行代码,并没有出错。

至于替代方案,下面的版本更"容易"(想到)。

sub to_nested_hash {
    $_[0] //= {};
    my $h     = shift;
    my $value = pop;
    eval '$h'.(join '', map "->{\$_[$i]}", 0..$#_).' = $value';
    return $h;
}

但是速度大约慢了6-7倍。


请您添加一下解释,好吗? - simbabque
+1 我没有想到通过对哈希元素取引用来自动创建它。 - Borodin
1
@Borodin,当我发现它可以工作时,这真是个天赐之物。我使用了一些多级哈希转换来进行一些数据处理,它将我最常用的例程加速了600%-1000%。 - Axeman
做了一个小改动,使得 my $h; to_nested_hash($h, @keys, $val); 能够正常工作。 - ikegami
进行了一些小更改,以确保诸如 foo barsystem("rm -rf /") 之类的键正常工作。 - ikegami

1
我认为这段代码更好 - 更容易移入类方法,并根据提供的参数选择性地设置值。否则,所选答案很整洁。
#!/usr/bin/env perl

use strict;
use warnings;
use YAML;

my $hash = {};

my @array = qw(key1 key2 lastKey);
my $val = [qw/some arbitrary data/];

print Dump to_nested_hash($hash, \@array, $val);
print Dump to_nested_hash($hash, \@array);
sub to_nested_hash {
    my ($hash, $array, $val) = @_;
    my $ref   = \$hash;
    my @path = @$array;
    print "ref: $ref\n";
    my $h     = $$ref;
    $ref      = \$$ref->{ $_ } foreach @path;
    $$ref     = $val if $val;
    return $h;
}

0

感谢提供这么好的东西!!!

我用递归的方式完成了它:

sub Hash2Array
{
  my $this = shift;
  my $hash = shift;

  my @array;
  foreach my $k(sort keys %$hash)
  {
    my $v = $hash->{$k};
    push @array,
      ref $v eq "HASH" ? $this->Hash2Array($v, @_, $k) : [ @_, $k, $v ];
  }

  return @array;
}

对于所有这些解决方案进行性能比较将会很有趣...


0

我做了一个更好的版本,比axeman的容易理解,至少对我来说没有->和\shift。三行代码没有子程序。

带有子程序

sub to_nested_hash {
    my $h=shift;
    my($ref,$value)=(\$h,pop);
    $ref=\%{$$ref}{$_} foreach(@_);
    $$ref=$value;
    return $h;
}

my $z={};
to_nested_hash($z,1,2,3,'testing123');

没有子程序

my $z={};

my $ref=\$z; #scalar reference of a variable which contains a hash reference
$ref=\%{$$ref}{$_} foreach(1,2,3); #keys
$$ref='testing123'; #value

#with %z hash variable just do double backslash to get the scalar reference
#my $ref=\\%z;

结果:

$VAR1 = {
          '1' => {
                   '2' => {
                            '3' => 'testing123'
                          }
                 }
        };

没有子程序的方法在我的电脑上无法编译。$ref=\%{$$ref}{$_} foreach(1,2,3); 存在问题。 - CJ7

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