遍历Perl中的多维哈希

时间:2008-10-01 23:07:33

标签: perl scripting multidimensional-array

如果perl中有多个维度的散列(或散列引用),并且想要遍历所有值,那么最好的方法是什么。换句话说,如果我们有 $ f-> {$ x} {$ y},我想要像

这样的东西
foreach ($x, $y) (deep_keys %{$f})
{
}

而不是

foreach $x (keys %f) 
    {
    foreach $y (keys %{$f->{$x}) 
    {
    }
}

8 个答案:

答案 0 :(得分:12)

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

快速search on CPAN引发了非常有用的Data::Walk。定义一个子程序来处理每个节点,然后进行排序

use Data::Walk;

my $data = { # some complex hash/array mess };

sub process {
   print "current node $_\n";
}

walk \&process, $data;

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

walk \&process, \%hash;

对于更全面的解决方案(但在CPAN中很难找到),使用Data::Visitor::Callback或其父模块 - 这样做的好处是可以让您更好地控制自己的工作,并且(仅限额外的) street cred)是用Moose写的。

答案 1 :(得分:11)

这是一个选项。这适用于任意深度哈希:

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 :(得分:6)

这听起来好像Data::DiverData::Visitor对你来说是好方法。

答案 3 :(得分:2)

请记住,Perl列表和散列不是具有维度,因此不能是多维的。 可以拥有的是一个哈希项,设置为引用另一个哈希或列表。这可以用来创建虚假的多维结构。

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

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函数。

答案 4 :(得分:2)

如果你总是拥有所有的键值,你也可以捏造多维数组,或者你只是不需要将各个级别作为单独的数组访问:

$arr{"foo",1} = "one";
$arr{"bar",2} = "two";

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

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

答案 5 :(得分:1)

如果你想做的就是操作数值,这很容易,但是如果你想操作键,你需要规定如何恢复关卡。

一个。例如,您可以将键指定为"$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 ) = @_;
    ...
}

这已在Perl 5.10中测试过。

答案 6 :(得分:1)

如果您正在处理超过两层深度的树数据,并且您发现自己想要走这棵树,那么您应该首先考虑如果您计划重新实现所有内容,那么您将为自己做更多的额外工作当有很多好的替代品可用时,你需要手动对哈希哈希进行哈希处理(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 :: IxHash或类似方法绑定哈希值,否则无法预测哈希元素的遍历顺序 - 再次,如果您要完成这么多工作,我建议使用树模块

答案 7 :(得分:1)

没有办法获得你描述的语义,因为foreach一次迭代列表一个元素。你必须让deep_keys返回一个LoL(列表列表)。即使这在任意数据结构的一般情况下也不起作用。可能存在不同级别的子哈希,其中一些级别可能是ARRAY refs等。

Perlish的做法是编写一个可以遍历任意数据结构并在每个“叶子”(即非参考值)上应用回调的函数。 bmdhacks' answer是一个起点。确切的功能会根据您在每个级别的操作而有所不同。如果你关心的只是叶子值,这是非常简单的。如果你关心那些让你走向成功的关键词,索引等,事情会变得更加复杂。