如果perl中有多个维度的散列(或散列引用),并且想要遍历所有值,那么最好的方法是什么。换句话说,如果我们有 $ f-> {$ x} {$ y},我想要像
这样的东西foreach ($x, $y) (deep_keys %{$f})
{
}
而不是
foreach $x (keys %f)
{
foreach $y (keys %{$f->{$x})
{
}
}
答案 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::Diver或Data::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是一个起点。确切的功能会根据您在每个级别的操作而有所不同。如果你关心的只是叶子值,这是非常简单的。如果你关心那些让你走向成功的关键词,索引等,事情会变得更加复杂。