Эффективно получать хеш-запись, только если она существует в Perl

Я довольно часто пишу такие фрагменты кода:

if (exists $myHash->{$key}) {
    $value = $myHash->{$key};
}

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

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

Это становится еще более неэффективным в многоуровневой структуре:

if (exists $myHash->{$key1} 
    && exists $myHash->{$key1}{$key2} 
    && exists $myHash->{$key1}{$key2}{$key3}) {

    $value = $myHash->{$key1}{$key2}{$key3};
}

Здесь я, предположительно, выполняю 9 хэш-поисков вместо 3!

Достаточно ли умен Perl, чтобы оптимизировать такой случай? Или есть какая-то другая идиома для получения значения хеша без автооживления записи или выполнения двух последовательных поисков?

Мне известен модуль автовивификации, но, если возможно, я ищу решение, не требующее XS модуль, который необходимо установить. Также у меня не было возможности опробовать этот модуль, и я не совсем уверен, что происходит в случае многоуровневого хеша — модуль говорит, что это:

$h->{$key}

вернет undef, если ключ не существует - значит ли это, что это:

$h->{$key1}{$key2}

умрет, если $key1 не существует, на том основании, что я пытаюсь разыменовать undef? Если это так, чтобы избежать этого, по-видимому, вам все равно нужно будет проводить многоуровневые тесты на существование.


person harmic    schedule 25.07.2014    source источник


Ответы (2)


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

if (my $v = $hash{$key}) {
    print "have $key => $v\n";
}

Так же:

if ( ($v = $hash{key1}) && ($v = $v->{key2}) ) { 
    print "Got $v\n";
}
person perreal    schedule 25.07.2014
comment
Это удивительно - я думал, что любое использование lvalue несуществующей хеш-записи добавит ключ к хешу (в данном случае со значением undef). Очевидно, я ошибаюсь. Удивительно, я не понял этого после многих лет написания кода на Perl! - person harmic; 25.07.2014
comment
автовивификация происходит только во вложенных доступах - person perreal; 25.07.2014
comment
@harmic, да, если хэш-запись является lvalue, она будет автоматически оживлена, но вы можете неправильно понимать значение lvalue. В этом случае хеш находится в правой части присваивания; это делает его rvalue, а не lvalue. Как говорит @perreal, автооживление записей хэша rvalue происходит только с вложенными доступами - например. $h->{foo}{bar}{baz} автоматически сделает что-то вроде $h->{foo}{bar} //= {}. - person tobyink; 25.07.2014
comment
@tobyink ой извините, это была опечатка, я имел в виду rvalue - person harmic; 25.07.2014
comment
@perreal: таким образом вы не проверяете, существует ли хеш-элемент существует, как запросил OP, вы проверяете, является ли $hash{$key} true, что является другим (более строгим ) Тест конечно. - person emazep; 25.07.2014

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

my $value = $hash{$key};

Для многоуровневого доступа промежуточные записи будут автоматически оживляться. например

my $value = $hash{a}{b};

создаст ссылку на пустой хэш, если $hash{a} еще не существует. (Если он существует и не является ссылкой на хэш, perl выдаст ошибку и умрет.) Чтобы избежать этого, вам нужно сначала проверить каждый уровень. Вы можете написать подпрограмму для проверки существования произвольно вложенных ключей.

sub safe_exists {
    my $x = shift;
    foreach my $k (@_) {
        no warnings 'uninitialized';
        return unless ref $x eq ref {};
        return unless exists $x->{$k};
        $x = $x->{$k};
    }
    return 1;
}

if (safe_exists(\%hash, qw(a b))) {...}

В зависимости от вашего алгоритма (и почему вы пытаетесь избежать автовивификации) блокировка вашего хэша может быть полезной альтернативой no autovivification или многоуровневым exists тестам.

use Hash::Util;

my %hash = (a => { b => 1 });
Hash::Util::lock_hash_recurse(%hash);

say $h{a}{b}; # 1
say $h{a}{c}; # error!

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

person Michael Carman    schedule 25.07.2014