jpfarmer has asked for the wisdom of the Perl Monks concerning the following question:

I frequently find myself using hashes to modify the value of a variable when a hash entry exists, otherwise set it to something else. For example, if I'm looking for a name to go with an ID number, I'll check the hash for the ID number. If that fails, the name should be "unknown".

I traditionally have done something like this: my $var = $hash{'key'}; $var = "something" unless $var; although I have more recently switched to my $var = $hash{'key'} || "something";, which I like even better. However, I still find myself wondering if there is a more elegant solution.

So, I'm wondering, is there a way to alter the value returned if a hash lookup fails? or have I stumbled on the Best Solution to my problem?

Replies are listed 'Best First'.
Re: Making a failed hash lookup return something other than undef
by sauoq (Abbot) on Nov 13, 2003 at 19:59 UTC
    have I stumbled on the Best Solution to my problem?

    No, actually your "solution" is not very good at all. Consider what will happen if $hash{'key'} is 0, undef, or an empty string. You should be using exists():

    $hash{'key'} = "something" unless exists $hash{'key'};

    I recall a node where BrowserUk John_M._Dlugosz suggested that exists() should return something more useful (like a reference to the element) in the case that the key did exist... but that wouldn't aid in the case where it doesn't.

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Making a failed hash lookup return something other than undef
by davido (Cardinal) on Nov 13, 2003 at 19:59 UTC
    With all of the solutions you provided in your original question, "auto vivification" will cause Perl to create that hash element you're testing, and just leaves its value undefined. Thus, the hash grows, and the key exists, though its value is undef. That's a problem.

    How about this?

    $var = exists( $hash{'key'} ) ? $hash{'key'} : "non-existant";

    That won't trigger auto-vivification of the hash key. And it accomplishes your goal of reliably returning either the key or something else if the key doesn't exist. Always use exists to check existance of a hash key. defined (or checking for a value) doesn't do what you want.


    Dave


    "If I had my life to live over again, I'd be a plumber." -- Albert Einstein

      Actually, auto-vivification of hash entries doesn't happen unless you do something like take a reference to the hash entry... for example by referring to something more deeply nested in the non-existent hash entry:

      #!/usr/bin/perl -l my %hash; my $foo = $hash{'foo'}; print exists $hash{'foo'} ? "yes" : "no"; # prints "no" my $bar = $hash{'bar'}->{'xxx'}; print exists $hash{'bar'} ? "yes" : "no"; # prints "yes" my $baz = \$hash{'baz'}; print exists $hash{'baz'} ? "yes" : "no"; # prints "yes"

      -- Mike

      --
      XML::Simpler does not require XML::Parser or a SAX parser. It does require File::Slurp.
      -- grantm, perldoc XML::Simpler

        However, the hash itself may be auto-vivified in spite of using exists().   In fact, the hash will be auto-vivified by the use in exists()!   This may be what davido was thinking about.   Check perlref for examples of how the end-point (the hash element) is protected from auto-vivification by the above constructs, by the leading components of the hash expression are not.
Re: Making a failed hash lookup return something other than undef
by thelenm (Vicar) on Nov 13, 2003 at 20:04 UTC

    The code you posted has a problem when a hash entry exists but has a false value like 0 or undef. The straightforward way to test whether a hash entry exists is to use exists, e.g.:

    my $var = exists $hash{'key'} ? $hash{'key'} : "unknown";

    This is not particularly pretty, but works, and I don't know of a more succinct way to do the same thing.

    -- Mike

    --
    XML::Simpler does not require XML::Parser or a SAX parser. It does require File::Slurp.
    -- grantm, perldoc XML::Simpler

Re: Making a failed hash lookup return something other than undef
by hardburn (Abbot) on Nov 13, 2003 at 19:58 UTC

    You could use a tied hash where FETCH checks that the key exists before returning it. If it doesn't exist, it returns a default value (perhaps supplied when you called tie. All the other tied hash methods access the internal hash directly.

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    : () { :|:& };:

    Note: All code is untested, unless otherwise stated

Re: Making a failed hash lookup return something other than undef
by blokhead (Monsignor) on Nov 13, 2003 at 20:03 UTC
    I could have sworn there was a CPAN module that did this. Tie::Hash::Default or something similarly-named. But I can't find it.

    Anyway, this isn't too hard to implement with tie:

    package Tie::Hash::Default; require Tie::Hash; our @ISA = qw/Tie::ExtraHash/; sub TIEHASH { my ($class, $default) = @_; bless [ {}, $default ] => $class; } sub FETCH { my ($self, $key) = @_; exists $self->[0]->{$key} ? $self->[0]->{$key} : $self->[1]; } ############# package main; ## -1 is the default value for nonexistant keys in %h tie my %h, 'Tie::Hash::Default', -1; $h{foo} = $h{bar} = 1; print "$_ => $h{$_}\n" for qw/foo bar baz/;
    Be careful with this though. You have to make sure you initialize hash values that you are going to alter. A lot of magic/useful things depend on the default value being undef:
    $h{asdf}++; ## $h{asdf} == 0 $h{jkl} .= "jlk"; ## $h{jkl} eq "-1jkl" use strict; $h{xyz}{blah} = 1; ## Can't use string ("-1") as a HASH ref ..
    ... although using exists on the tied hash will still work as expected.

    blokhead