在Perl中遍历多维哈希

时间:2020-03-06 15:00:09  来源:igfitidea点击:

如果我们在perl中有一个具有多个维度的哈希(或者对哈希的引用),并且想要遍历所有值,那么做这件事的最佳方法是什么。换句话说,如果我们有
$ f-> {$ x} {$ y},我想要类似

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

代替

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

解决方案

如果只想对值进行操作就很容易,但是如果要对键进行操作,则需要规范如何恢复级别。

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

b。或者,我们可能有一个键列表。

我推荐后者。

  • 级别可以通过@ $ 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中进行了测试。

请记住,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函数。

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

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";
});

如果我们要处理的树数据深度超过两个级别,并且发现自己想遍历那棵树,那么如果我们打算重新实现所需的一切,那么首先应该考虑要为自己做很多额外的工作。当有很多不错的替代方法可用时,手动对哈希值进行哈希处理(在CPAN中搜索"树")。

不知道数据需求实际上是什么,我将盲目地指向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或者类似的方法再次绑定哈希,否则我们无法预测哈希元素将以什么顺序遍历,如果我们要进行大量工作,我建议我们使用树模块。

无法获得描述的语义,因为" foreach"一次遍历列表中的一个元素。我们必须让deep_keys代替返回一个LoL(列表列表)。即使在任意数据结构的一般情况下,这也不起作用。子哈希的级别可能有所不同,某些级别可能是ARRAY参考,等等。

这样做的Perlish方法是编写一个可以遍历任意数据结构并在每个"叶子"(即非引用值)处应用回调的函数。 bmdhacks的回答是一个起点。确切的功能可能会有所不同,具体取决于我们希望在每个级别上执行的操作。如果我们只关心叶子值,那将非常简单。如果我们关心使我们步入正轨的键,索引等,事情将会变得更加复杂。

如果我们始终拥有所有键值,或者我们不需要将各个级别作为单独的数组进行访问,则还可以对多维数组进行修饰:

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

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

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

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

快速搜索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或者其父模块,这样做的好处是可以更好地控制操作,并且(仅用于获得额外的信誉) )是使用Moose编写的。

在我看来,Data :: Diver或者Data :: Visitor对我们来说是不错的选择。