Десериализация вложенного хэша из списков ключей хеша

У меня есть строка, которую я хотел бы "расстегнуть" или "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 так ошибся? К сожалению, мне удалось довольно легко придумать обратную функцию (сгладить).

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;
Другие вопросы по тегам