在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对我们来说是不错的选择。