Десериализация вложенного хэша из списков ключей хеша
У меня есть строка, которую я хотел бы "расстегнуть" или "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;