Как я могу аккуратно превратить вложенный хеш Perl в не вложенный? - PullRequest
7 голосов
/ 22 марта 2010

Предположим, что вложенная структура хеша %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?Будь резким!: -)
  • Как бы ты это очистил?

Ответы [ 4 ]

10 голосов
/ 23 марта 2010

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

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

В качестве заключительного замечания, обычно более эффективно передавать хэши по ссылке, чтобы избежать их развертывания в списки, а затем снова превращать их в хэши.

3 голосов
/ 23 марта 2010

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

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

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

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

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

2 голосов
/ 23 марта 2010

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

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;

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

1 голос
/ 23 марта 2010

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...