如何将嵌套的Perl哈希干净地转换为非嵌套的?

时间:2010-03-22 20:52:16

标签: perl

假设嵌套哈希结构%old_hash ..

my %old_hash;
$old_hash{"foo"}{"bar"}{"zonk"} = "hello";

..我们希望使用子&flatten(...)“展平”(抱歉,如果这是错误的术语!)到非嵌套哈希,以便...

my %h = &flatten(\%old_hash);
die unless($h{"zonk"} eq "hello");

&flatten(...)的以下定义可以解决问题:

sub flatten {
  my $hashref = shift;
  my %hash;
  my %i = %{$hashref};
  foreach my $ii (keys(%i)) {
    my %j = %{$i{$ii}};
    foreach my $jj (keys(%j)) {
      my %k = %{$j{$jj}};
      foreach my $kk (keys(%k)) {
        my $value = $k{$kk};
        $hash{$kk} = $value;
      }
    }
  }
  return %hash;
}

虽然给出的代码有效,但它不是非常易读或干净。

我的问题是双重的:

  • 给定代码在哪些方面与现代Perl最佳实践不对应?要严厉! : - )
  • 你会如何清理它?

4 个答案:

答案 0 :(得分:10)

您的方法不是最佳做法,因为它无法扩展。如果嵌套哈希是六个,十个级别深度怎么办?重复应该告诉你,递归例程可能就是你需要的。

sub flatten {
    my ($in, $out) = @_;
    for my $key (keys %$in) {
        my $value = $in->{$key};
        if ( defined $value && ref $value eq 'HASH' ) {
            flatten($value, $out);
        }
        else {
            $out->{$key} = $value;
        }
    }
}

或者,良好的现代Perl风格是尽可能使用CPAN。 Data::Traverse可以满足您的需求:

use Data::Traverse;
sub flatten {
    my %hash = @_;
    my %flattened;
    traverse { $flattened{$a} = $b } \%hash;
    return %flattened;
}

作为最后一点,通过引用传递哈希值通常更有效,以避免它们被扩展到列表中,然后再次变为哈希值。

答案 1 :(得分:3)

首先,我会使用perl -c来确保它干净地编译,而不是。所以,我会添加一个尾随}来进行编译。

然后,我将通过perltidy运行它以改进代码布局(缩进等)。

然后,我会运行perlcritic(以“苛刻”模式)自动告诉我它认为不好的做法。它抱怨说:

  

子程序不以“return”结束

更新:在我发布上述答案之后,OP基本上改变了每行代码,但我相信它仍然适用。拍摄移动目标并不容易:)

答案 2 :(得分:2)

您需要弄清楚您的方法存在的一些问题。首先,如果有两个叶节点具有相同的密钥,会发生什么?如果输出包含它们的列表,那么第二个clobber是第一个被忽略吗?这是一种方法。首先,我们使用递归函数构造一个关键值对的平面列表,以处理其他散列深度:

my %data = (
    foo  => {bar  => {baz  => 'hello'}},
    fizz => {buzz => {bing => 'world'}},
    fad  => {bad  => {baz  => 'clobber'}},
);


sub flatten {
    my $hash = shift;
    map {
        my  $value = $$hash{$_};
        ref $value eq 'HASH' 
            ? flatten($value) 
            : ($_ =>  $value)
    } keys %$hash
}

print join( ", " => flatten \%data), "\n";
# baz, clobber, bing, world, baz, hello

my %flat = flatten \%data;

print join( ", " => %flat ), "\n";
# baz, hello, bing, world          # lost (baz => clobber)

修复可能是这样的,它将创建包含所有值的数组引用的散列:

sub merge {
    my %out;
    while (@_) {
        my ($key, $value) = splice @_, 0, 2;
        push @{ $out{$key} }, $value
    }
    %out
}

my %better_flat = merge flatten \%data;

在生产代码中,在函数之间传递引用会更快,但为了清楚起见,我在此省略了它。

答案 3 :(得分:1)

您是否打算最终获得原始哈希的副本或只是重新排序的结果?

您的代码以一个哈希(引用使用的原始哈希)开头,并生成两个副本%i%hash

声明my %i=%{hashref}不是必需的。您正在将整个哈希复制到新哈希。在任何一种情况下(无论您是否需要副本),您都可以使用对原始哈希的引用。

如果哈希中的哈希值与父哈希值相同,那么您也会丢失数据。这是为了吗?