如果您在 perl 中有一个具有多个维度的散列(或对散列的引用)并且您想要遍历所有值,那么最好的方法是什么。换句话说,如果我们有 $f->{$x}{$y},我想要类似的东西
foreach ($x, $y) (deep_keys %{$f})
{
}
代替
foreach $x (keys %f)
{
foreach $y (keys %{$f->{$x})
{
}
}
如果您在 perl 中有一个具有多个维度的散列(或对散列的引用)并且您想要遍历所有值,那么最好的方法是什么。换句话说,如果我们有 $f->{$x}{$y},我想要类似的东西
foreach ($x, $y) (deep_keys %{$f})
{
}
代替
foreach $x (keys %f)
{
foreach $y (keys %{$f->{$x})
{
}
}
第一阶段:不要重新发明轮子:)
在 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或其父模块 - 这具有让您更好地控制您所做的事情的优势,并且(仅用于额外的街道cred) 是使用 Moose 编写的。
这是一个选项。这适用于任意深度的哈希:
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";
});
在我看来,这听起来好像Data::Diver或Data::Visitor是适合您的好方法。
请记住,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 函数。
如果您始终拥有所有键值,或者您只是不需要将各个级别作为单独的数组访问,您也可以捏造多维数组:
$arr{"foo",1} = "one";
$arr{"bar",2} = "two";
while(($key, $value) = each(%arr))
{
@keyValues = split($;, $key);
print "key = [", join(",", @keyValues), "] : value = [", $value, "]\n";
}
这使用下标分隔符“$;” 作为键中多个值的分隔符。
如果您只想对值进行操作,这很容易,但如果您想对键进行操作,则需要指定如何恢复级别。
一种。例如,您可以将键指定为"$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 中进行了测试。
如果您正在处理超过两层深度的树数据,并且您发现自己想要走那棵树,那么您应该首先考虑如果您计划重新实现所需的一切,您将为自己做很多额外的工作当有很多好的替代方案可用时(在CPAN 中搜索“Tree”),手动对散列的散列进行散列处理。
不知道您的数据需求实际上是什么,我将盲目地向您介绍 Tree::DAG_Node 的教程以帮助您入门。
也就是说,Axeman 是正确的,hashwalk 最容易通过递归完成。如果您觉得绝对必须使用散列的散列来解决您的问题,这里有一个示例可以帮助您入门:
#!/usr/bin/perl 使用严格; 使用警告; 我的 %hash = ( “顶层 1” => { "sublevel1a" => "value-1a", "sublevel1b" => "value-1b" }, “顶层 2” => { "sublevel1c" => { “价值-1c.1”=>“替换-1c.1”, “价值-1c.2”=>“替换-1c.2” }, "sublevel1d" => "value-1d" } ); 散列行走(\%hash); 子哈希步道 { 我的 ($element) = @_; 如果(参考($元素)=〜/哈希/) { foreach 我的 $key (keys %$element) { 打印 $key," => \n"; hashwalk($$element{$key}); } } 别的 { 打印$元素,"\n"; } }
它将输出:
顶层 2 => sublevel1d => 价值1d sublevel1c => 价值 1c.2 => 替换-1c.2 价值 1c.1 => 替换-1c.1 顶层 1 => 子层1a => 价值 1a 底层1b => 价值-1b
请注意,除非您通过 Tie::IxHash 或类似方法绑定哈希,否则您无法预测哈希元素的遍历顺序——同样,如果您要完成那么多工作,我推荐使用树模块。
无法获得您描述的语义,因为foreach
一次迭代一个列表元素。您必须改为deep_keys
返回一个 LoL(列表列表)。即使这在任意数据结构的一般情况下也不起作用。可能有不同级别的子哈希,其中一些级别可能是 ARRAY refs 等。
Perlish 的做法是编写一个可以遍历任意数据结构并在每个“叶子”(即非引用值)处应用回调的函数。bmdhacks 的回答是一个起点。确切的功能会有所不同,具体取决于您想要在每个级别执行的操作。如果您只关心叶子值,那将非常简单。如果您关心使您进入叶子的键、索引等,事情会变得更加复杂。