在Perl中递归打印数据结构

5

我正在学习Perl。我有一个包含对哈希和数组的引用的Perl哈希表。这些哈希和数组可能反过来包含对其他哈希/数组的引用。

我编写了一个子例程来递归解析哈希并以适当的缩进打印它们。虽然该例程按预期工作,但我的讲师并不认为以下代码的可读性和优雅性。

我非常希望在此处得到Perl专家的意见,以优化以下代码。

这是我的完整代码片段。

# Array of Arrays
$ref_to_AoA = [
     [ "fred", "barney" ],
     [ "george", "jane", "elroy" ],
     [ "homer", "marge", "bart" ],
];


#Array of Hashes
$ref_to_AoH = [ 
{
   husband  => "barney",
   wife     => "betty",
   son      => "bamm bamm",
},
{
   husband => "george",
   wife    => "jane",
   son     => "elroy",
},
];

# Hash of Hashes
$ref_to_HoH = { 
    flintstones => {
        husband   => "fred",
        pal       => "barney",
    },
    jetsons => {
       husband   => "george",
       wife      => "jane",
       "his boy" => "elroy",  # Key quotes needed.
    },
    simpsons => {
       husband   => "homer",
       wife      => "marge",
       kid       => "bart",
    },
};

# Hash which contains references to arrays and hashes
$finalHash = {
   'arrayofArrays' => $ref_to_AoA,
   'arrayofHash' => $ref_to_AoH,
   'hashofHash' => $ref_to_HoH,
};

$string = str($finalHash); 
print "$string\n";

#------------------------------------------------------------------
sub str {
    my $hash = shift;
    my ($space, $newline, $delimiter) = @_;
    $space = "" unless (defined $space);
    $newline = "\n\n\n" unless (defined $newline);
    $delimiter = "\n--------------------------------------------" unless (defined $delimiter);
    my $str = "";

    for (sort keys %{$hash}) {
        my $value = $hash->{$_};
        $str .= "$newline$space$_ == $value$delimiter";
        $str .= recurseErrors($value,$space);
    }
    $str;
}

#------------------------------------------------------------------
sub recurseErrors {
    my $str;
    my ($value,$space) = @_;
    my $ref = ref $value;

    if ($ref eq 'ARRAY') {
        my $i = 0;
        my $isEmpty = 1;
        my @array = @$value;
        $space .= "\t";
        for my $a (@array) {
            if (defined $a) {
                $isEmpty = 0;
                $str .= "\n$space$_\[$i\] :";
                $str .= recurseErrors($a,$space);
            }
            $i++;
        }
        $str .= "= { }" if ($isEmpty);

    } elsif ($ref eq 'HASH') {
        $space .= "\t";
        for my $k (sort keys %$value) {
            if ( ( ref($value->{$k}) eq 'HASH') || (ref $value->{$k} eq 'ARRAY') ) {
                my $val = $value->{$k};
                $str .= "\n\n$space$k == ";
                $str .= "$val";
            }
            else {
                $str .= "\n$space$k == ";
            }
            $str .= recurseErrors($value->{$k},$space);
      }

      # we have reached a scalar (leaf)
    } elsif ($ref eq '') {
        $str .= "$value";
    }
$str
}
#------------------------------------------------------------------

输出:

arrayofArrays == ARRAY(0x9d9baf8)
--------------------------------------------
    arrayofArrays[0] :
        arrayofArrays[0] :fred
        arrayofArrays[1] :barney
    arrayofArrays[1] :
        arrayofArrays[0] :george
        arrayofArrays[1] :jane
        arrayofArrays[2] :elroy
    arrayofArrays[2] :
        arrayofArrays[0] :homer
        arrayofArrays[1] :marge
        arrayofArrays[2] :bart
arrayofHash == ARRAY(0x9d9bba8) -------------------------------------------- arrayofHash[0] : husband == barney son == bamm bamm wife == betty arrayofHash[1] : husband == george son == elroy wife == jane
hashofHash == HASH(0x9da45f8) --------------------------------------------
flintstones == HASH(0x9d9bb48) husband == 弗雷德 pal == 巴尼
jetsons == HASH(0x9d9bbf8) his boy == 埃尔罗伊 husband == 乔治 wife == 简
simpsons == HASH(0x9d9bc48) husband == 霍默 kid == 巴特 wife == 玛吉

1
你的讲师有点儿幽默。你的演示看起来很好。 - brian d foy
你确定你的教练不是在指输出吗? - Brad Gilbert
8个回答

8

也许Data::Dumper正是您所需要的:

use Data::Dumper;

$str = Dumper($foo);
print($str);

8
我相信他的导师不会对此感到高兴。 - innaM

8
  1. 始终使用use strict;
  2. 为成为一个好孩子,还应该使用use warnings
  3. 您为子例程使用的名称应清楚地说明子例程的功能。"recurseErrors"有点违反了这个原则。是的,它确实递归了。但是什么错误呢?
  4. 在每个子例程的第一行中,您应声明并初始化任何参数。recurseErrors首先声明$str,然后声明其参数。
  5. 不要像在str()中那样混合使用shift和=@_。
  6. 您可以考虑将现在称为recurseErrors的内容分解为专门处理数组和哈希的例程。
  7. 没有必要像第99行和第109行那样引用变量。

除此之外,我认为你的教练那天心情不太好。


3
这是一套不错的遵循规则。我们能否请求原帖发布者更新其改进后的任务?我发现有关代码风格和设计的讨论非常有趣且富有启发性。 - Leonardo Herrera

6
如果您是perl的新手,建议您通过perl-critic来运行您的代码(也可以从CPAN安装脚本,通常我使用它作为测试,因此当我执行“make test”时,它会从命令行运行)。除了其输出外,您可能还想将您的函数分解得更细一些。recurseErrors有三种情况,可以拆分为子函数(甚至可以放入引用类型到子函数引用的哈希表中)。
如果这是一个生产任务,我会使用Data::Dumper,但听起来这是一份作业,所以您的老师可能不会太高兴。

3
这是一个简单的例子,说明你的代码不易读:
$delimiter = "\n--------------------------------------------" unless (defined $delimiter);

您可以使用“defined or”运算符:
$delimiter //= "\n" . '-' x 44;

如果您担心早期的Perl版本:

defined $delimeter or $delimeter = "\n" . '-' x 44;

如果条件语句超出右边界,足以让我失去兴趣,不想再读下去。


1

我曾经遇到过同样的问题,并在这里找到了解决方法。我差点使用了这里发布的一个解决方案,但最终发现了一个更适合我的解决方案。可以在这里阅读有关深度优先递归的内容。

上述文章中的子程序完美地处理了包含其他哈希、数组或标量的引用。但它没有打印哈希键名,所以我稍微修改了一下:

#!/usr/bin/perl
#
# See:
#
# http://perldesignpatterns.com/?DepthFirstRecursion
#
use strict;
use warnings;

my %hash = (
  'a' => {
    'one' => 1111,
    'two' => 222,
  },
  'b' => [ 'foo', 'bar' ],
  'c' => 'test',
  'd' => {
    'states' => {
      'virginia' => 'richmond',
      'texas' => 'austin',
    },
    'planets' => [ 'venus','earth','mars' ],
    'constellations' => ['orion','ursa major' ],
    'galaxies' => {
      'milky way' => 'barred spiral',
      'm87' => 'elliptical',
    },
  },
);

&expand_references2(\%hash);

sub expand_references2 {
  my $indenting = -1;
  my $inner; $inner = sub {
    my $ref = $_[0];
    my $key = $_[1];
    $indenting++;
    if(ref $ref eq 'ARRAY'){
      print '  ' x $indenting,'ARRAY:';
      printf("%s\n",($key) ? $key : '');
      $inner->($_) for @{$ref};
    }elsif(ref $ref eq 'HASH'){
      print '  ' x $indenting,'HASH:';
      printf("%s\n",($key) ? $key : '');
      for my $k(sort keys %{$ref}){
        $inner->($ref->{$k},$k);
      }
    }else{
      if($key){
        print '  ' x $indenting,$key,' => ',$ref,"\n";
      }else{
        print '  ' x $indenting,$ref,"\n";
      }
    }
    $indenting--;
  };
  $inner->($_) for @_;
}

1

你可以将处理数组和哈希表的代码块分开。

sub recurse{
  ...
  recurse_A(@_) if $ref eq 'ARRAY';
  recurse_H(@_) if $ref eq 'HASH';
  ...
}

sub recurse_A{ ... }
sub recurse_H{ ... }

我建议你这样开始你的子程序,除非你有充分的理由做出其他选择。
sub example{
  my( $one, $two, $three, $optional_four ) = @_;

( 如果你这样做,那么Komodo至少可以弄清楚你子程序的参数是什么 )

很少有理由将一个变量放入只包含该变量的字符串中。

"$var" eq $var;

我能想到唯一需要这样做的情况是当我使用一个有重载 "" 函数的对象,并且我想要获取字符串,而不想获取对象。
package My_Class;
use overload
  '""' => 'Stringify',
;
sub new{
  my( $class, $name ) = @_;
  my $self = bless { name => $name }, $class;
  return $self;
}
sub Stringify{
  my( $self ) = @_;
  return $self->{name};
}

my $object = My_Class->new;
my $string = "$object";

1

我猜他不喜欢你:

  1. str 函数中期望哈希值。
  2. 调用相同的函数将数组打印为哈希表,尽管它们之间似乎没有共同的函数。
  3. 允许以各种方式调用 str,但它从未出现在最终结果中。
  4. 允许传入可配置空格到根函数中,但在递归函数中硬编码了一个制表符。
  5. 省略实际上在数组中占据位置的未定义值。

这些是我能够很快看到的问题。


0
    #use strict ; 
    use warnings ; 
    # use module
    use XML::Simple;
    use Data::Dumper;

    #debug print "START SCRIPT " ; 

    my $fileToParse = 'C:/Temp/CDIP/scripts/perl/nps_all_workflows.xml' ; 

    # create object
    my $objXml= new XML::Simple;

    # read XML file
    my $data = $objXml->XMLin("$fileToParse");

    # #debug print "\n FirstLevel is " . $objXml->{'POWERMART'} ; 
    my $level = 1 ; 

    #
    printHashKeyValues ($data ) ;  


    sub printHashKeyValues  
    {
        $level ++ ; 
        my $refHash = shift ; 
        my $parentKey = shift ; 
        my $parentValue = shift ; 


        while( my ($key, $value) = each %$refHash) 
        {


                        if ( defined ( $key ) )
                        {
                                if ( ref ($refHash->{"$key"}) eq 'HASH'  ) 
                                 {
                                 my $newRefHash = $refHash->{"$key"} ; 
                                 #debug print " \n The key is a hash " ; 
                                 printHashKeyValues ($newRefHash , $key , $value) ;
                                 }


                                    if ( ref ($refHash->{"$key"}) eq 'ARRAY'  ) 
                                     {
                                            #debug print " \n the key is an ARRAY " ; 
                                            printArrayValues ( $refHash->{"$key"} ) ; 
                                     }


                        } #eof  if ( defined ( $key ))

                        if ( defined ( $value) )
                        {

                                if (  ref ($refHash->{"$value"}) eq 'HASH'  ) 
                                 {
                                 my $newRefHash = $refHash->{"$value"} ; 
                                #debug print " \n The value is a hash " ; 
                                 printHashKeyValues ($newRefHash , $key , $value) ;
                                 }


                                if ( ref ($refHash->{"$value"}) eq 'ARRAY'  ) 
                                 {
                                        #debug print " \n the value is an ARRAY " ; 
                                        printArrayValues ( $refHash->{"$value"} ) ; 
                                 }

                         } #eof if defined ( $value ) 

                                #debug print "\n key: $key, value: $value.\n";


        } #eof while
    } #eof sub 



    sub printArrayValues
    {
        my $arrRef = shift ; 
        my @array = @$arrRef; 
        my $parrentArrayElement = shift ; 

            #debug print "printArrayValues CALLED " ;

        foreach my $arrayElement ( @array ) 
        {
                        if (defined ( $arrayElement ) )
                        {
                                if   ( ref ($arrayElement) eq 'HASH'  ) 
                             {
                                 #debug print " \n The  \$arrayElement is a hash FROM THE ARRAY " ; 
                                 printHashKeyValues ($arrayElement ) ;  
                             } #eof if 

                                if   ( ref ($arrayElement) eq 'ARRAY'  ) 
                             {
                                 #debug print " \n The \$arrayElement is a ARRAY  FROM THE ARRAY " ; 
                                 printArrayValues ($arrayElement ) ;  
                             } #eof if 

                                #debug print "\n \$arrayElement is $arrayElement " ; 
                        } #eof if ( defined ( $arrayElement ) ) 



        } #eof foreach 

    } #eof sub 




    # #debug print output
    ##debug print Dumper($data);




    1 ; 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接