在Perl中遍历多维哈希
如果我们在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对我们来说是不错的选择。

