【问题标题】:Perl hash of hashes of hashes of hashes... is there an 'easy' way to get an element at the end of the list?Perl hash of hashes of hashes...有没有一种“简单”的方法来获取列表末尾的元素?
【发布时间】:2012-11-15 17:58:43
【问题描述】:

我有一个 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 循环集,如果我不必再重复六次左右,那就太好了。

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

【问题讨论】:

  • 听起来您的代码将受益于不同的数据模型。我很难想象对应于 11 或 12 级哈希的真实情况。例如,即使一个人有多个名字,他们肯定不应该有多个年龄吗?所以AGE 应该只是个人级别的另一个哈希键。也许使用 Perl 的面向对象功能会有所帮助:broadcast.oreilly.com/2008/11/…
  • @WinnieNicklaus,XML 通常有 12 层深的节点。
  • 这只是一个示例数据集,它实际上并不与人相关,但我用它只是为了显示代码的结构。数据对应于数据库的一部分,指的是一组在早期最好减少的id,否则会导致程序在以后变慢。它冗长乏味,但适用于它所代表的:)
  • 这使得想出正确的变量名变得不可能。你能给出一些有意义的东西吗?
  • 国家->县->城市->街道->房屋->人(姓名,年龄)

标签: perl hash-of-hashes


【解决方案1】:

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

foreach my $person_key (sort keys %$people) {
    my $person = $people->{$person_key};
    my $names  = $person->{NAMES};
    foreach my $name (sort keys %$names) {

【讨论】:

  • 啊,这是个好主意!我可以$names = $people->{$person_key}{NAMES};
【解决方案2】:

您也可以使用 每个 关键字。这肯定会有所帮助。

while( my ($person, $val1) = each(%$people) ) {
    while( my ($name, $val2) = each(%$val1) ) {
        while( my ($age, $val3) = each(%$val2) ) {
            print $val3->{Somekey};

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      示例数据

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

      根据您的问题,我得出的结论是您只想在叶子上工作(在这种情况下为AGEs)。因此,可以编写一个递归的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);
          }
      }
      

      我认为这个人是如何通过内联 cmets 工作的很清楚。如果您愿意,在所有节点上执行 coderef 而不是只在叶子上执行 coderef 不会有太大的变化。请注意,为了方便起见,我在这里特别添加了一个 prototype,因为它很容易使用 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)
      

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

      my @flattened_people = ();
      
      traverse {
          my ($thing, @path) = @_;
          push @flattened_people, { age => $thing, path => \@path };
      } \%people;
      

      【讨论】:

        猜你喜欢
        • 2016-02-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-28
        • 1970-01-01
        相关资源
        最近更新 更多