6

我有一个散列的 Perl 散列......大约 11 或 12 个元素深。请原谅我不重复下面的结构!

一些级别具有固定标签,例如'NAMES''AGES'或类似的标签,因此访问这些级别很好,因为我可以直接使用标签,但我需要遍历其他变量,这会导致一些非常长的语句。这是一组循环的一半示例:

foreach my $person (sort keys %$people) {
        foreach my $name (sort keys %{$people->{$person}{'NAMES'}}) {
            foreach my $age (sort keys %{$people->{$person}{'NAMES'}{$name}{'AGES'}}) {
                . . . # and so on until I get to the push @list,$element; part

这只是一个例子,但它遵循我的结构。没有固定名称部分(大写的元素)可能会更短,但在其他地方需要它们以供参考。

我尝试将元素转换为散列以在每个阶段缩短它,例如对于第二个 foreach 我尝试了各种形式:

foreach my $name (sort keys %{$person->{'NAMES'}})

但这没有用。我敢肯定我以前见过类似的东西,所以语义可能不正确。

我研究了有关散列哈希的页面以及对散列及其元素的引用等等,但没有运气。我见过while each循环的例子,但它们似乎并不是特别短或更容易实现。也许只有一种不同的方法可以做到这一点,我没有抓住重点。我已经写出完整的foreach循环集一次,如果我不必再重复六次左右,那就太好了。

当然,可能没有“简单”的方法,但感谢所有帮助!

4

4 回答 4

6

$person是关键,要缩短内部循环的内容,您需要将值分配给某些东西:

foreach my $person_key (sort keys %$people) {
    my $person = $people->{$person_key};
    my $names  = $person->{NAMES};
    foreach my $name (sort keys %$names) {
于 2012-11-15T18:12:34.610 回答
5

您也可以使用每个关键字。这绝对应该有所帮助。

while( my ($person, $val1) = each(%$people) ) {
    while( my ($name, $val2) = each(%$val1) ) {
        while( my ($age, $val3) = each(%$val2) ) {
            print $val3->{Somekey};
于 2012-11-15T18:37:19.670 回答
4

如果您想构建一个更灵活的解决方案,您可以递归地遍历数据树。考虑这个示例数据树(任意深度):

示例数据

my %people = (
    memowe => {
        NAMES => {
            memo        => {AGE => 666},
            we          => {AGE => 667},
        },
    },
    bladepanthera => {
        NAMES => {
            blade       => {AGE => 42},
            panthera    => {AGE => 17},
        },
    },
);

根据您的问题,我得出的结论是您只想在叶子上工作(AGE在这种情况下为 s )。因此,可以编写一个递归traverse子例程,在它可能以键排序深度优先顺序找到的所有叶子上执行给定的子引用。为方便起见,此子引用获取 leave 本身和哈希键路径:

准备工作

sub traverse (&$@) {
    my ($do_it, $data, @path) = @_;

    # iterate
    foreach my $key (sort keys %$data) {

        # handle sub-tree
        if (ref($data->{$key}) eq 'HASH') {
            traverse($do_it, $data->{$key}, @path, $key);
            next;
        }

        # handle leave
        $do_it->($data->{$key}, @path, $key);
    }
}

我认为从内联的评论中可以很清楚地知道这个人是如何工作的。如果您愿意,在所有节点上执行 coderef 而不是只在叶子上执行 coderef 不会有太大的变化。请注意,为了方便起见,我在这里特别添加了一个原型,因为它很容易traverse与众所周知的mapgrep语法一起使用:

在你的数据上执行东西

traverse { say shift . " (@_)" } \%people;

另请注意,它适用于哈希引用,我们@path使用隐式空列表初始化 。

输出

42 (bladepanthera NAMES blade AGE)
17 (bladepanthera NAMES panthera AGE)
666 (memowe NAMES memo AGE)
667 (memowe NAMES we AGE)

给定的子程序(写成 a { block })可以对给定的数据做任何事情。例如这个更易读的 push 子程序:

my @flattened_people = ();

traverse {
    my ($thing, @path) = @_;
    push @flattened_people, { age => $thing, path => \@path };
} \%people;
于 2012-11-15T18:52:38.897 回答
4

您可以使用Data::Walk,它是一种用于数据结构的 File::Find。

于 2012-11-15T22:46:56.837 回答