代码之家  ›  专栏  ›  技术社区  ›  David Nehme

在Perl中遍历多维哈希

  •  5
  • David Nehme  · 技术社区  · 16 年前

    如果您在Perl中有一个具有许多维度的哈希(或对哈希的引用),并且您希望遍历所有值,那么最好的方法是什么。换句话说,如果我们有 $F->$X$Y,我想要类似的东西

    foreach ($x, $y) (deep_keys %{$f})
    {
    }
    

    而不是

    foreach $x (keys %f) 
        {
        foreach $y (keys %{$f->{$x}) 
        {
        }
    }
    
    8 回复  |  直到 16 年前
        1
  •  11
  •   bmdhacks    16 年前

    这里有一个选择。这适用于任意深度散列:

    sub deep_keys_foreach
    {
        my ($hashref, $code, $args) = @_;
    
        while (my ($k, $v) = each(%$hashref)) {
            my @newargs = defined($args) ? @$args : ();
            push(@newargs, $k);
            if (ref($v) eq 'HASH') {
                deep_keys_foreach($v, $code, \@newargs);
            }
            else {
                $code->(@newargs);
            }
        }
    }
    
    deep_keys_foreach($f, sub {
        my ($k1, $k2) = @_;
        print "inside deep_keys, k1=$k1, k2=$k2\n";
    });
    
        2
  •  12
  •   Jonathon Reinhart    16 年前

    第一阶段:不要重新发明轮子:)

    快速 search on CPAN 扔掉非常有用的 Data::Walk . 定义一个子例程来处理每个节点,然后对其进行排序

    use Data::Walk;
    
    my $data = { # some complex hash/array mess };
    
    sub process {
       print "current node $_\n";
    }
    
    walk \&process, $data;
    

    鲍勃是你叔叔。请注意,如果您想给它传递一个hash-to-walk,则需要传递一个对它的引用(请参见 perldoc perlref ,如下所示(否则它也会尝试处理哈希键!):

    walk \&process, \%hash;
    

    要获得更全面的解决方案(但在CPAN中乍一看很难找到),请使用 Data::Visitor::Callback 或者它的父模块——这有一个优点,可以让你更好地控制你所做的事情,而且(只是为了额外的街头信誉)是用驼鹿写的。

        3
  •  6
  •   Solyad    16 年前

    这听起来好像 Data::Diver Data::Visitor 是你的好方法。

        4
  •  2
  •   Zan Lynx    16 年前

    请记住,Perl列表和哈希表不会 维度等等不能是多维的。你什么 可以 have是设置为引用另一个哈希或列表的哈希项。这可以用来创建假多维结构。

    一旦你意识到这一点,事情就变得容易了。例如:

    sub f($) {
      my $x = shift;
      if( ref $x eq 'HASH' ) {
        foreach( values %$x ) {
          f($_);
        }
      } elsif( ref $x eq 'ARRAY' ) {
        foreach( @$x ) {
          f($_);
        }
      }
    }
    

    当然,除了遍历结构之外,还要添加其他需要做的事情。

    一个很好的方法来做你需要做的事情是通过一个代码引用从F内部调用。通过使用子原型,你甚至可以使调用看起来像Perl的grep和map函数。

        5
  •  2
  •   Greg Cottman    16 年前

    如果您总是拥有所有的键值,或者您只是不需要将各个级别作为单独的数组来访问,那么也可以对多维数组进行篡改:

    $arr{"foo",1} = "one";
    $arr{"bar",2} = "two";
    
    while(($key, $value) = each(%arr))
    {
        @keyValues = split($;, $key);
        print "key = [", join(",", @keyValues), "] : value = [", $value, "]\n";
    }
    

    这将使用下标分隔符“$;”作为键中多个值的分隔符。

        6
  •  1
  •   Community Paul Sweatte    7 年前

    无法得到你描述的语义,因为 foreach 一次迭代一个列表元素。你必须 deep_keys 返回一个LOL(列表列表)。即使在任意数据结构的一般情况下,这也不起作用。可能存在不同级别的子散列,其中一些级别可能是数组引用等。

    实现这一点的Perlish方法是编写一个函数,该函数可以遍历任意数据结构,并在每个“叶”(即非引用值)上应用回调。 bmdhacks' answer 是一个起点。具体的功能将根据您在每个级别想要做的事情而有所不同。如果你只关心叶子的价值观,那就很简单了。如果你关心那些把你带到叶子上的键、索引等,事情就会变得更复杂。

        7
  •  1
  •   community wiki 5 revs Axeman    16 年前

    如果您只想对值进行操作就足够简单了,但是如果您想对键进行操作,就需要规范如何恢复级别。

    例如,可以将键指定为 "$level1_key.$level2_key.$level3_key" --或表示级别的任何分隔符。

    或者你可以有一张钥匙清单。

    我推荐后者。

    • 水平可以理解为 @$key_stack

    • 最本地的密钥是 $key_stack->[-1] .

    • 路径可以通过以下方式重建: join( '.', @$key\_stack )

    代码:

    use constant EMPTY_ARRAY => [];
    use strict;    
    use Scalar::Util qw<reftype>;
    
    sub deep_keys (\%) { 
        sub deeper_keys { 
            my ( $key_ref, $hash_ref ) = @_;
            return [ $key_ref, $hash_ref ] if reftype( $hash_ref ) ne 'HASH';
            my @results;
    
            while ( my ( $key, $value ) = each %$hash_ref ) { 
                my $k = [ @{ $key_ref || EMPTY_ARRAY }, $key ];
                push @results, deeper_keys( $k, $value );
            }
            return @results;
        }
    
        return deeper_keys( undef, shift );
    }
    
    foreach my $kv_pair ( deep_keys %$f ) { 
        my ( $key_stack, $value ) = @_;
        ...
    }
    

    这已经在Perl5.10中进行了测试。

        8
  •  1
  •   community wiki 2 revs, 2 users 99% Zed    16 年前

    如果你正在处理超过两个层次的树数据,并且你发现你自己想要走那棵树,你应该首先考虑,如果你计划在有很多好的替代方法可用的情况下,在哈希的哈希上手动重新实现你需要做的每一件事,你将为自己做很多额外的工作。( search CPAN for "Tree" )

    不知道你的数据需求实际上是什么,我会盲目地把你指向 tutorial for Tree::DAG_Node 开始吧。

    也就是说,axeman是正确的,哈希遍历最容易用递归完成。下面是一个例子,如果你觉得你必须用哈希的哈希来解决你的问题:

    #!/usr/bin/perl
    use strict;
    use warnings;
    
    my %hash = (
        "toplevel-1" => 
        { 
            "sublevel1a"  => "value-1a",
            "sublevel1b"  => "value-1b"
        },
        "toplevel-2" =>
        {
            "sublevel1c" => 
            {
                "value-1c.1" => "replacement-1c.1",
                "value-1c.2" => "replacement-1c.2"
            },
            "sublevel1d" => "value-1d"
        }
    );
    
    hashwalk( \%hash );
    
    sub hashwalk
    {
        my ($element) = @_;
        if( ref($element) =~ /HASH/ )
        {
            foreach my $key (keys %$element)
            {
                print $key," => \n";
                hashwalk($$element{$key});
            }
        }
        else
        {
            print $element,"\n";
        }
    }
    

    它将输出:

    toplevel-2 => 
    sublevel1d => 
    value-1d
    sublevel1c => 
    value-1c.2 => 
    replacement-1c.2
    value-1c.1 => 
    replacement-1c.1
    toplevel-1 => 
    sublevel1a => 
    value-1a
    sublevel1b => 
    value-1b
    

    请注意,您不能预测哈希元素的遍历顺序,除非您再次通过tie::ixshash或类似的_来绑定哈希,如果您要完成那么多的工作,我建议您使用树模块。