Дополнительный поиск хеша с использованием «существует»?

Иногда я обращаюсь к такому хэшу:

if(exists $ids{$name}){
    $id = $ids{$name};
}

Это хорошая практика? Я немного обеспокоен тем, что он содержит два поиска, где на самом деле нужно сделать один. Есть ли лучший способ проверить существование и присвоить значение?


person Frank    schedule 10.06.2009    source источник


Ответы (6)


Проверяя с помощью exists, вы предотвращаете автоживификацию. См. раздел Автовивификация: что это такое и зачем мне это?.

ОБНОВЛЕНИЕ: Как указывает trendels ниже, в приведенном вами примере автовивификация не работает. . Я предполагаю, что фактический код включает многоуровневые хэши.

Вот иллюстрация:

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my (%hash, $x);

if ( exists $hash{test}->{vivify} ) {
    $x = $hash{test}->{vivify}->{now};
}

print Dumper \%hash;

$x = $hash{test}->{vivify}->{now};

print Dumper \%hash;

__END__


C:\Temp> t
$VAR1 = {
    'test' => {}
};
$VAR1 = {
    'test' => {
        'vivify' => {}
    }
};
person Sinan Ünür    schedule 10.06.2009
comment
Является ли exists дешевле, чем фактическое получение значения? В конце концов, ему не нужно следовать связанному списку, когда он находит коллизию. - person Frank; 10.06.2009
comment
Однако в данном конкретном случае хэш-ключ для $name не будет создан автовивификацией. Только попытка доступа к ключу, вложенному на один уровень глубже, например $id = $ids{$name}{other}, создаст ключ $name. - person trendels; 10.06.2009
comment
@trendels Верно, но я предположил, что ОП слишком упростил. Тем не менее, я должен был указать на это. - person Sinan Ünür; 10.06.2009
comment
Спасибо за ссылку. Я не знал об этом. - person Nathan Fellman; 10.06.2009

Вы можете применить Hash ::Используйте lock_keys для хеша. Затем выполните свои задания в рамках eval.

#!/usr/bin/perl
use Hash::Util qw/lock_keys/;

my %a = (
    1 => 'one',
    2 => 'two'
);

lock_keys(%a);

eval {$val = $a{2}};     # this assignment completes
eval {$val = $a{3}};     # this assignment aborts
print "val=$val\n";      # has value 'two'
person dwarring    schedule 11.06.2009

Вы можете сделать это с помощью одного поиска следующим образом:

$tmp = $ids{$name};
$id = $tmp if (defined $tmp);

Тем не менее, я бы не стал беспокоиться, если бы не увидел, что это узкое место.

person Nathan Fellman    schedule 10.06.2009
comment
Хорошо, но на самом деле это не совсем то же самое. exists проверяет, есть ли значение (может быть undef), тогда как Defined проверяет, есть ли значение и оно не является undef. - person Frank; 10.06.2009
comment
У вас есть точка зрения, но в конце концов, если она не существует или существует, но не определена, вы получите undef. Вы видите здесь спектакль, который вас так беспокоит, или это чисто академическое? Спрашиваю просто из любопытства, больше ничего... - person Nathan Fellman; 10.06.2009
comment
Чисто академический! Мне просто не понравилось то, что я дважды проверяю хэш. Я изменю его и просто проверю defined, как вы предложили. - person Frank; 10.06.2009

если это не многоуровневый хэш, вы можете сделать это:

$id = $ids{$name} || 'foo';

или если $id уже имеет значение:

$id ||= $ids{$name};

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

person user118435    schedule 11.06.2009
comment
p.s. ниток нет. Вопросы имеют ответы, ответы и вопросы имеют комментарии. Нет потоков. - person Kent Fredric; 11.06.2009
comment
также пс. раньше нет, смотрите старые, новые и вкладки голосов, которые смешивают порядок. - person Kent Fredric; 11.06.2009
comment
Что происходит, когда $ids{$name} равно 0, пусто или не определено? - person innaM; 27.07.2009

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

my %h;
for my $key (@some_vals) {
  ...
  $h{$key} = undef unless exists $h{$key};
  ...
}

return keys %h;

Этот код немного быстрее, чем обычно используемый $h{$key}++. exists избегает бесполезного присвоения, а undef избегает выделения по значению. Лучший ответ для вас: Сравните это! Я предполагаю, что exists $ids{$name} немного быстрее, чем $id=$ids{$name}, и если у вас большой коэффициент промаха, ваша версия с существующими может быть быстрее, чем задание и проверка после.

Например, если мне нужно быстрое пересечение наборов, я бы написал что-то вроде этого.

sub intersect {
  my $h;
  @$h{@{shift()}} = ();
  my $i;
  for (@_) {
    return unless %$h;
    $i = {};
    @$i{grep exists $h->{$_}, @$_} = ();
    $h = $i;
  }
  return keys %$h;
}
person Hynek -Pichi- Vychodil    schedule 11.06.2009
comment
Создание ключей, которых еще нет в хеше, обычно не является хорошей идеей. Вы не хотите начинать хранить ключи, которые вам не нужны. - person brian d foy; 14.06.2009
comment
Вы неправильно понимаете. Попробуйте снова. - person Hynek -Pichi- Vychodil; 14.06.2009
comment
Это слово означает не то, что вы думаете. - person Ether; 18.06.2009

производительность в этом случае не важна, см. "Devel::NYTProf". Но чтобы ответить на ваш вопрос:

если значение в хеше не существует, "существует" очень быстро

if(exists $ids{$name}){
    $id = $ids{$name};
}

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

$id = $ids{$name};
if($id){
    #....
}

см. этот небольшой тест из списка рассылки perl.

#!/usr/bin/perl -w
use strict;
use Benchmark qw( timethese );

use vars qw( %hash );
@hash{ 'A' .. 'Z', 'a' .. 'z' } = (1) x 52;

my $key = 'xx';
timethese 10000000, {
        'defined' => sub {
                if (defined $hash{$key}) { my $x = $hash{$key}; return $x; };
                return 0;
        },
        'defined_smart' => sub {
                my $x = $hash{$key};
                if (defined $x) {
                        return $x;
                };
                return 0;
        },
        'exists' => sub {
                if (exists $hash{$key}) { my $x = $hash{$key}; return $x; };
                return 0;
        },
        'as is' => sub {
                if ($hash{$key}) { my $x = $hash{$key}; return $x; };
                return 0;
        },
        'as is_smart' => sub {
                my $x = $hash{$key};
                if ($x) { return $x; };
                return 0;
        },

};

использование несуществующего ключа ("xx") показывает, что "существует" является победителем.

Benchmark: timing 10000000 iterations of as is, as is_smart, defined, defined_smart, exists...
     as is:  1 wallclock secs ( 1.52 usr +  0.00 sys =  1.52 CPU) @ 6578947.37/s (n=10000000)
as is_smart:  3 wallclock secs ( 2.67 usr +  0.00 sys =  2.67 CPU) @ 3745318.35/s (n=10000000)
   defined:  3 wallclock secs ( 1.53 usr +  0.00 sys =  1.53 CPU) @ 6535947.71/s (n=10000000)
defined_smart:  3 wallclock secs ( 2.17 usr +  0.00 sys =  2.17 CPU) @ 4608294.93/s (n=10000000)
    exists:  1 wallclock secs ( 1.33 usr +  0.00 sys =  1.33 CPU) @ 7518796.99/s (n=10000000)

использование существующего ключа («x») показывает, что «as is_smart» является победителем.

Benchmark: timing 10000000 iterations of as is, as is_smart, defined, defined_smart, exists...
     as is:  3 wallclock secs ( 2.76 usr +  0.00 sys =  2.76 CPU) @ 3623188.41/s (n=10000000)
as is_smart:  3 wallclock secs ( 1.81 usr +  0.00 sys =  1.81 CPU) @ 5524861.88/s (n=10000000)
   defined:  3 wallclock secs ( 3.42 usr +  0.00 sys =  3.42 CPU) @ 2923976.61/s (n=10000000)
defined_smart:  2 wallclock secs ( 2.32 usr +  0.00 sys =  2.32 CPU) @ 4310344.83/s (n=10000000)
    exists:  3 wallclock secs ( 2.83 usr +  0.00 sys =  2.83 CPU) @ 3533568.90/s (n=10000000)
person key_    schedule 20.08.2013