views:

304

answers:

5

I sometimes access a hash like this:

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

Is that good practice? I'm a bit concerned that it contains two lookups where really one should be done. Is there a better way to check the existence and assign the value?

+1  A: 

You can do it with one lookup like this:

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

However, I wouldn't bother unless I saw that that was a bottleneck

Nathan Fellman
Okay, but that's actually not exactly the same. `exists` checks if there is a value (may be undef), whereas defined checks if there is a value and it is not undef.
You have a point, but at the end of the day if it doesn't exist or if it exists but is undefined, you'll get an undef. Are you seeing a performance hit here that you're so concerned about it, or is this purely academic? I ask just out of curiosity, nothing else...
Nathan Fellman
Purely academic! I just didn't like the fact that I am checking the hash twice. I'll change it and just do a `defined` check, like you suggested.
+9  A: 

By checking with exists, you prevent autovivification. See Autovivification : What is it and why do I care?.

UPDATE: As trendels points out below, autovivification does not come into play in the example you posted. I am assuming that the actual code involves multi-level hashes.

Here is an illustration:

#!/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' => {}
    }
};
Sinan Ünür
Is `exists` cheaper than actually retrieving the value? After all, it doesn't have to follow a linked list when it finds a collision.
In this particular case, though, the hash key for "$name" would *not* be created by autovivification. Only trying to access a key nested one level deeper, like "$id = $ids{$name}{other}" would create the "$name" key.
trendels
@trendels Correct but I assumed the OP had over-simplified. Still, I should have pointed that out.
Sinan Ünür
Thanks for the link. I wasn't aware of that.
Nathan Fellman
A: 

if it is not a multi-level hash you can do this:

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

or if $id already has a value:

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

where 'foo' is a default or fall-through value. If it is a multi-level hash you would use 'exists' to avoid the autovivification discussed earlier in the thread or not use it if autovivification is not going to be a problem.

p.s. there are no "threads". Questions have answers, answers and questions have comments. No Threads.
Kent Fredric
also ps. there is no "earlier", see "older", "newer" and "votes" tabs which mash the order.
Kent Fredric
What happens when $ids{$name} is 0 or empty or undef?
innaM
+1  A: 

You could use apply Hash::Util's lock_keys to the hash. Then perform your assignments within an 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'
snoopy
A: 

If I want high performance I'm used to write this idiom when want create hash as set:

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

return keys %h;

This code is little bit faster than commonly used $h{$key}++. exists avoids useless assignment and undef avoids allocation for value. Best answer for you is: Benchmark it! I guess that exists $ids{$name} is little bit faster than $id=$ids{$name} and if you have big miss ratio your version with exists can be faster than assignment and test after.

For example if I want fast sets intersection I would wrote something like this.

sub intersect {
  my $h;
  @$h{@{shift()}} = ();
  my $i;
  for (@_) {
    return unless %$h;
    $i = {};
    @$i{grep exists $h->{$_}, @$_} = ();
    $h = $i;
  }
  return keys %$h;
}
Hynek -Pichi- Vychodil
Creating keys that aren't already in the hash is usually not a good idea. You don't want to start storing keys that you don't want.
brian d foy
You misapprehend. Try it again.
Hynek -Pichi- Vychodil
That word does not mean what you think it means.
Ether