【问题标题】:How to create recursive hash of hash ? (With unlimited deep)如何创建哈希的递归哈希? (无限深)
【发布时间】:2016-09-21 09:08:25
【问题描述】:

拜托,我尝试创建一个复杂的数据结构。我知道该怎么做

$branch{'level1'}{'level2'}{'level3'}='leaf';

但我不知道如何创建

$branch{'level1'}....{'levelN'}='leaf';

我尝试这样的事情:

$branch{'leaf'} = "1";
$branchREF = \%branch;
$branchtmp{'level3'} = $branchREF;

所以我成功得到:

$VAR1 = 'level3';
$VAR2 = {
          'leaf' => '1'
        };

但是对于下一步,做一个递归的 N 哈希,我尝试:

%branch = %branchtmp;

但结果完全错误...%branch 不是我所期望的。为了进行递归,我需要重用我的第一个 %branch 而不是创建一个新的。请问我该怎么办?

一个。

【问题讨论】:

    标签: perl data-structures


    【解决方案1】:

    我强烈建议使用现有的解决方案,例如 Data::Diver

    use Data::Diver qw( DiveVal );
    
    my @keys = map "level$_", 1 .. 3;
    
    my $branch = {};
    DiveVal($branch, map \$_, @keys) = 'leaf';
      -or-
    my %branch;
    DiveVal(\%branch, map \$_, @keys) = 'leaf';
    

    显然,它也可以在没有模块的情况下完成。

    sub DiveVal :lvalue {
       my $p = \shift;
       $p = \( $$p->{$_} ) for @_;
       $$p
    }
    
    my @keys = map "level$_", 1 .. 3;
    
    my $branch;
    DiveVal($branch, @keys) = 'leaf';
      -or-
    my %branch;
    DiveVal(\%branch, @keys) = 'leaf';
    

    我的DiveVal 是如何工作的:

    Pre-loop:          $p references $branch
    After loop pass 0: $p references $branch->{level1}
    After loop pass 1: $p references $branch->{level1}{level2}
    After loop pass 2: $p references $branch->{level1}{level2}{level3}
    Returned:          $branch->{level1}{level2}{level3}
    

    额外的间接层有很多好处。

    • 它消除了对最后一个键进行特殊处理的需要。
    • 它消除了在取消引用之前创建哈希的需要。
    • 它不再需要根作为对哈希的引用。相反,任何标量都可以是根,甚至是未定义的。
    • 它可以轻松扩展 DiveVal 以支持混合数组/哈希结构。

    【讨论】:

    • 非常感谢您的回答,我从中学到了很多。我还想链接到documentation of :lvalue。我需要它:)
    • @ikegami,如果您是 Data::Diver 的作者,我已经通过 CPAN 向您发送了一封电子邮件,即发送到您的 cpan.org 电子邮件地址。我不确定电子邮件是否会转发到您的真实电子邮件地址。
    • @Bulrush,我不维护该模块。我的 CPAN 名称与我的 SO 名称相同。是的,CPAN 地址是邮件转发器。
    【解决方案2】:

    Data::Diver可以帮你:

    #!/usr/bin/perl
    use warnings;
    use strict;
    
    use Data::Diver qw{ DiveVal };
    use Data::Dumper;
    
    my %branch;
    DiveVal(\%branch, map "level$_", 1 .. 3) = 'leave';
    print Dumper \%branch;
    

    输出:

    $VAR1 = {
              'level1' => {
                            'level2' => {
                                          'level3' => 'leave'
                                        }
                          }
            };
    

    或者,如果你想自己实现它:

    #!/usr/bin/perl
    use warnings;
    use strict;
    
    use Data::Dumper;
    
    sub set_value {
        my ($struct, @list) = @_;
        if (@list > 2) {
            set_value($struct->{ $list[0] } = {}, @list[ 1 .. $#list ]);
        } else {
            $struct->{ $list[0] } = $list[1];
        }
    }
    
    
    my %branch;
    set_value(\%branch, map("level$_", 1 .. 3), 'leave');
    print Dumper \%branch;
    

    【讨论】:

    • 谢谢 Choroba。我更喜欢您的第二种方式,无需额外的模块,但我发现 eballes 方式更简单一些。谢谢您的回答 ! :)
    • @choroba,您可能有兴趣查看this
    【解决方案3】:

    最简单的方法是记住 perl 如何处理多维数据结构。它通过引用来做到这一点。因此,您可能会发现从顶层开始更简单,而不是作为哈希引用:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    use Data::Dumper;
    
    my @levels = qw ( level1 level2 level3 );
    
    my $branch = {};
    
    my $tail = pop(@levels);
    my $cursor = $branch;
    #iterate our levels
    foreach my $level (@levels) { 
       #make a new anon-hash if there isn't one. 
       $cursor -> {$level} ||= {};
       #traverse down
       $cursor = $cursor->{$level};
    }
    #set a value
    $cursor -> {$tail} = 'leaf';
    
    print Dumper $branch;
    

    输出:

    $VAR1 = {
              'level1' => {
                            'level2' => {
                                          'level3' => 'leaf'
                                        }
                          }
            };
    

    注意 - 您必须重置光标并“重新遍历”才能再次执行此操作,但可以以类似的方式“行走”结构。

    【讨论】:

    • 如果您将$cursor = ...; 替换为$cursor = \( ... ); 并调整$cursor 的所有其他实例,这可以大大简化。见我的answer
    【解决方案4】:

    一个简单的递归解决方案:

    #!/usr/bin/env perl   
    use strict;
    use warnings;
    use Data::Dumper;
    
    sub recursiveHash {
        my ($result, @rest) = @_;
        return $result unless @rest;
    
        my $nextTag = pop @rest;
        return recursiveHash( {$nextTag => $result}, @rest);
    }
    
    print Dumper(recursiveHash('leaf', 'level1', 'level2', 'level3'));
    

    输出:

    $VAR1 = {
              'level1' => {
                            'level2' => {
                                          'level3' => 'leaf'
                                        }
                          }
            };
    

    也就是说,Perl 中的子程序调用相当慢。幸运的是,这里完全不需要递归。

    sub iterativeHash {
        my ($result, @rest) = @_;
        while (@rest) {
           my $nextTag = pop @rest;
           $result = { $nextTag => $result };
        }
        return $result;
    }
    

    【讨论】:

    • 每个人的回答速度让我印象深刻!我真的很喜欢你回答它的方式:我不需要额外的模块,我发现递归方式非常优雅。所以我得到了你的答案,但是来自 Sobrique 和 Choroba 的那个也非常好。谢谢 ! :)
    • 很遗憾,这种方法不能用于添加到现有结构中,例如DiveVal($root, @keys) = $leaf; 可以。
    【解决方案5】:

    我使用 DBM::Deep,您可能必须自己安装它。我需要一些可以处理长键值和大约 100,000 个键的东西。普通 Perl 散列(默认安装的散列)仅处理 1008 字节的组合键和数据长度。


    # LIMITS:
    # - Key size limit: none
    # - Data size limit: limited by OS
    # - Number of keys per hash: millions
    # - Number of nested levels: millions. "Can handle millions of keys and unlimited levels without significant slow-down. "
    
    # Basic
    use Fcntl; # Required for hashes.
    use DBM::Deep;
    my($db);
    my $fn='file.dbm';
    $db=DBM::Deep->new($fn) or die "Could not make DBM::Deep in file $fn";
    $db->{'key1'}="stuff";
    delete $db->{'key1'};
    if ($db->exists("key"))
        {
        print "Yay\n";
        }
    $db->clear(); # Delete all keys.
    @k=keys(%$db); # Get all first level keys.
    
    # Alternate
    my $fn='file.dbm';
    tie my %db, 'DBM::Deep', $fn or die "Could not tie hash to $fn";
    $db{key} = 'value';
    print $db{key};
    
    tied(%db)->put('key' => 'value');
    print tied(%db)->get('key');
    
    # Multi-level hash
    $db->{'key1'}->{'subkey1'}="more stuff";
    $db->{'wine'}->{'red'}->{'taste'}="good";
    $db->{'invoice'}->{'16738'}->{'customernum'}=455;
    
    # get keys or values
    @k=keys(%$db);
    @k=values(%$db);
    @k=keys( %{$dbm->{'A'}} );  # Gets subkeys for 'A'
    

    【讨论】:

      猜你喜欢
      • 2011-09-20
      • 2011-04-20
      • 2013-12-20
      • 1970-01-01
      • 2015-10-06
      • 2018-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多