5

如果您在 perl 中有一个具有多个维度的散列(或对散列的引用)并且您想要遍历所有值,那么最好的方法是什么。换句话说,如果我们有 $f->{$x}{$y},我想要类似的东西

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

代替

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

8 回答 8

12

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

在 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 编写的。

于 2008-10-02T08:36:49.907 回答
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";
});
于 2008-10-02T00:07:11.783 回答
6

在我看来,这听起来好像Data::DiverData::Visitor是适合您的好方法。

于 2008-10-02T08:43:08.470 回答
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 函数。

于 2008-10-01T23:44:35.333 回答
2

如果您始终拥有所有键值,或者您只是不需要将各个级别作为单独的数组访问,您也可以捏造多维数组:

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

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

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

于 2008-10-02T04:31:11.433 回答
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 中进行了测试。

于 2008-10-01T23:24:05.227 回答
1

如果您正在处理超过两层深度的树数据,并且您发现自己想要走那棵树,那么您应该首先考虑如果您计划重新实现所需的一切,您将为自己做很多额外的工作当有很多好的替代方案可用时(在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 或类似方法绑定哈希,否则您无法预测哈希元素的遍历顺序——同样,如果您要完成那么多工作,我推荐使用树模块。

于 2008-10-02T00:08:24.467 回答
1

无法获得您描述的语义,因为foreach一次迭代一个列表元素。您必须改为deep_keys返回一个 LoL(列表列表)。即使这在任意数据结构的一般情况下也不起作用。可能有不同级别的子哈希,其中一些级别可能是 ARRAY refs 等。

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

于 2008-10-02T01:43:25.250 回答