Как я могу чисто превратить вложенный хеш Perl в невложенный?

Примите вложенную структуру хеша %old_hash ..

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

.. который мы хотим "сгладить" (извините, если это - неправильная терминология!) к невложенному хешу с помощью sub &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? Будьте резки!:-)
  • Как Вы очистили бы его?
7
задан brian d foy 23 March 2010 в 17:31
поделиться

4 ответа

Ваш метод не является оптимальным, потому что он не масштабируется. Что, если глубина вложенного хэша составляет шесть или десять уровней? Повторение должно сказать вам, что, вероятно, вам нужна рекурсивная процедура.

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;
}

И последнее замечание: обычно более эффективно передавать хэши по ссылке, чтобы они не разворачивались в списки, а затем снова превращались в хеши.

10
ответ дан 6 December 2019 в 12:48
поделиться

Есть несколько проблем с вашим подходом, которые вам нужно решить. Во-первых, что произойдет, если есть два листовых узла с одним и тем же ключом? Второй тупит первый, второй игнорируется, должен ли вывод содержать их список? Вот один из подходов. Сначала мы создаем плоский список пар ключ-значение, используя рекурсивную функцию для работы с другой глубиной хеширования:

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;

В производстве код, было бы быстрее передавать ссылки между функциями, но я опустил это здесь для ясности.

2
ответ дан 6 December 2019 в 12:48
поделиться

Вы намерены получить копию исходного хэша или просто переупорядочить результат?

Ваш код начинается с одного хэша (исходный хэш, который используется в качестве ссылки) и создает две копии % i и % hash .

Оператор my% i =% {hashref} не нужен. Вы копируете весь хеш в новый хеш. В любом случае (независимо от того, нужна ли вам копия) вы можете использовать ссылки на исходный хэш.

Вы также теряете данные, если ваш хэш в хеше имеет то же значение, что и родительский хеш. Это предназначено?

1
ответ дан 6 December 2019 в 12:48
поделиться

Во-первых, я бы использовал perl -c, чтобы убедиться, что он компилируется чисто, чего он не делает. Так Я бы добавил замыкающий }, чтобы сделать его скомпилируемым.

Затем я запускал его через perltidy, чтобы улучшить макет кода (отступ и т. Д.).

Затем я запускал perlcritic (в «жестком» режиме), чтобы автоматически сказать мне, что он считает плохой практикой. Он жалуется, что:

Подпрограмма не заканчивается на "return"

Update: OP по существу изменил каждую строку кода после того, как я опубликовал свой ответ выше, но я считаю, что он все еще применим. Нелегко стрелять по движущейся мишени :)

3
ответ дан 6 December 2019 в 12:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: