【问题标题】:Deserializing a nested hash from lists of hash keys从散列键列表中反序列化嵌套散列
【发布时间】:2014-07-28 10:00:00
【问题描述】:

我有一个字符串我想“unflatten”或“tree-ify”;也就是说,我想从这个出发:

F=8|A_C=3|A_B=2|D_G_H=11|D_B=2|E=5  

到这里:

{
  A => {
    B => 2,
    C => 3,
  },
  D => {
     B => 2,
     G => {
       H => 11,
     },
  },
  E => 5,
  F => 8,
}

我的策略是分别处理每个以竖线分隔的字段,并通过= 符号拆分为键/值对:

sub unflatten {
    my ($data) = @_;
    my @fields = split /\|/, $data;
    my $result = {};

    for my $datum (@fields) {
            my ($key, $value) = split /=/, $datum;
            $result->{&processline($key)} = $value;
    }
    return $result;
}

我在 processline 函数中尝试了一些递归魔法:

sub processline {
    my ($key) = @_;

    my ($first, $rest) = split /_/, $key, 2; # split key into at most 2 parts
    if($rest) {
            return { $first => &processline($rest) }; 
            # if the key is nested, there will be something in $rest
            # so recursively process the smaller $rest, and build up the result hashref
    }
    else {
            return $first;
    }
}

很遗憾,这不起作用:

my $header = "F=8|A_C=3|A_B=2|D_G_H=11|D_B=2|E=5";
use Data::Dumper;
print Dumper &unflatten($header);

当我这样做时,我得到:

$VAR1 = {
      'F' => '8',
      'HASH(0xe9af60)' => '2',
      'HASH(0xe9ae28)' => '11',
      'E' => '5',
      'HASH(0xe9af90)' => '3',
      'HASH(0xe9ae40)' => '2'
    };

有人可以解释递归解决方案背后的思考过程,或者建议我的 Perl 哪里出了这么大的问题吗?令人沮丧的是,我能够很容易地想出这个函数的逆函数(展平)。

【问题讨论】:

  • 您从 processline 返回的密钥不正确,请参见:ideone.com/28apW3
  • 附带说明,您的&sub() 呼叫中的& 是不需要的。它告诉 Perl 忽略原型设计,而你没有。你可以安全地放下它。有关详细信息,请参阅stackoverflow.com/questions/1347396/…。

标签: perl recursion unfold


【解决方案1】:

我相信使用简单的for 循环比使用递归更简单。您选择的方法不起作用,因为它仅使用 processline 返回的单个键来分配值,并且无法创建多级哈希。

递归解决方案的工作方式是获取哈希引用、键列表和值,然后定义

unflatten($hash, 'key1_key2_key3_key4', 'value')

作为

unflatten($hash->{key1}, 'key2_key3_key4', 'value')`

这个程序演示了一个简单的循环解决方案。它使用一个指针$hash,该指针从生成的哈希的根开始,并在列表中的每个键之后向前移动一个级别。

sub unflatten {

  my $result = {};

  for my $item (split /\|/, $_[0]) {

    my ($keys, $value) = split /=/, $item;
    my @keys = split /_/, $keys;
    my $hash = $result;

    while (@keys > 1) {
      my $key = shift @keys;
      $hash->{$key} ||= {};
      $hash = $hash->{$key};
    }

    $hash->{$keys[0]} = $value;
  }

  return $result;
}

输出

$VAR1 = {
      'A' => {
               'C' => '3',
               'B' => '2'
             },
      'F' => '8',
      'D' => {
               'G' => {
                        'H' => '11'
                      },
               'B' => '2'
             },
      'E' => '5'
    };

更新

现在我回到了键盘,这是一个递归解决方案。它会产生与原始哈希相同的哈希

use strict;
use warnings;

use Data::Dumper;

my $data = 'F=8|A_C=3|A_B=2|D_G_H=11|D_B=2|E=5';

my $result = {};
unflatten2($result, $_) for split /\|/, $data;
print Dumper $result;

sub unflatten2 {
  my ($hash, $data) = @_;

  if ($data =~ /_/) {
    my ($key, $rest) = split /_/, $data;
    unflatten2($hash->{$key} ||= {}, $rest);
  }
  else {
    my ($key, $val) = split /=/, $data;
    $hash->{key} = $val;
  }
}

更新

您可能还对 Data::Diver 模块感兴趣,该模块适用于此类情况,尽管文档有点笨拙

这是使用它的解决方案的外观

use strict;
use warnings;

use Data::Diver qw/ DiveVal /;
use Data::Dumper;

my $data = 'F=8|A_C=3|A_B=2|D_G_H=11|D_B=2|E=5';

my $result = {};    

for (split /\|/, $data) {
  my ($keys, $val) = split /=/;
  DiveVal($result, split /_/, $keys) = $val;
}

print Dumper $result;

【讨论】:

  • 感谢您非常详细的回答。我曾尝试过类似你的 unflatten2 的东西,但再次为每行返回一个密钥,因此从未到达那里。太棒了!
  • @galvatron:我很高兴它有用。我使用Data::Diver 模块编写了另一个您可能感兴趣的更新
  • @Miller:幸运的是,Perl 的优化消除了在分配给固定长度列表时显式指定$_ 和 LIMIT 值的需要。看看perl -MO=Deparse -e'($x) = split' 为证!但请注意,像$x = (split)[2] 这样的东西还是写成$x = (split $_, 3)[2] 更好
  • 是的,确实如此。然而,我实际上鼓励限制不是为了效率,而是因为如果用户假设希望值中有一个等号怎么办。说出其中的实际含义会使代码在默认情况下更加健壮,这就是为什么我会在这里放弃第二种解决方案,因为通过更改操作顺序,它使得 _ 不能在值中。
  • @Miller:啊,我明白你的意思了。我很同情,但我倾向于使用 die if /=.*_/ or tr/=// > 1 来清理数据,因为没有办法正确执行此操作并允许在键中使用等号
猜你喜欢
  • 2012-07-22
  • 2013-03-14
  • 2013-07-08
  • 2013-01-30
  • 2020-02-03
  • 1970-01-01
  • 1970-01-01
  • 2021-11-03
  • 2017-10-02
相关资源
最近更新 更多