http://www.perlmonks.org?node_id=684059

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

Is there a way to define a default value for a hash? For example, if you access the hash with any of the known keys, the expected data gets retrieved, but can you define a special default value so that if you try and access the hash with an unrecognized key, it always returns that special value? Here is an example of what I would like to do.
my %description = ( a => 'a vowel', b => 'a consonant', '' => 'not in the alphabet' #description of anything else ); print("a is $description{'a'}\n"); print("b is $description{'b'}\n"); print("~ is $description{'~'}"); #Would like the following: #"~ is not in the alphabet"
This doesn't work, but hopefully it's clear what I would like to happen.

Any help will be greatly appreciated!

Replies are listed 'Best First'.
Re: Default Hash Key
by ikegami (Patriarch) on May 02, 2008 at 01:05 UTC
    It can also be done using a tied hash (with a speed penalty). I don't see an existing module on CPAN after a quick look, but it's it's easy to write your own.
    { package Hash::WithDefault; use Tie::Hash qw( ); BEGIN { our @ISA = 'Tie::ExtraHash'; } use constant IDX_HASH => 0; use constant IDX_DEFAULT => 1; use constant NEXT_IDX => 2; sub FETCH { my ($self, $key) = @_; return ( exists( $self->[IDX_HASH]{$key} ) ? $self->[IDX_HASH]{$key} : $self->[IDX_DEFAULT] ); } } { tie my %hash, 'Hash::WithDefault', 'not in the alphabet'; %hash = ( a => 'a vowel', b => 'a consonant', ); print("$_: $hash{$_}\n") for qw( a b ~ ); }

    Output:

    a: a vowel b: a consonant ~: not in the alphabet
        Thanks for looking, but that module doesn't do what the OP wants as far as I can tell.
      If I read the doc of Tie::Hash correctly, you should have used
      use Tie::StdHash qw( );
      Tie::Hash only provides a subset of the necessary hash methods

        Tie::Hash, Tie::StdHash and Tie::ExtraHash are in Tie/Hash.pm, thus use Tie::Hash; is needed even if you want to use the Tie::StdHash class.

        >perl -e"use Tie::StdHash qw( );" Can't locate Tie/StdHash.pm in @INC (@INC contains: c:/Progs/perl588/l +ib c:/Progs/perl588/site/lib .) at -e line 1. BEGIN failed--compilation aborted at -e line 1.

        Notice that I didn't actually inherit from Tie::Hash. I didn't inherit from Tie::StdHash either because it doesn't provide any means to store object attributes such as the default value. Tie::ExtraHash does.

Re: Default Hash Key
by oko1 (Deacon) on May 02, 2008 at 02:28 UTC

    Just in case you have an 'XY' problem in your question, here's a common solution for this class of thing:

    my %description = ( a => 'a vowel', b => 'a consonant' ); for my $choice (qw/a b c/){ print "$choice is ", $description{$choice} || "not in the alphabet. +", "\n"; }

    Output:

    a is a vowel b is a consonant c is not in the alphabet.
    
    -- 
    Human history becomes more and more a race between education and catastrophe. -- HG Wells
    
      Wow thanks for the reply okol (and for the benefit of the doubt!) I really learned a lot from all the replies, but yours is nice and simple and probably about as fast as possible.

      I tried just a slight variation and I think it's going to work nicely.
      use strict; use warnings; my %description = ( a => 'a vowel', b => 'a consonant', default => 'not in the alphabet' ); for my $char (qw/a b c/) { my $d = $description{$char} || $description{default}; print("$char is $d\n"); }
      I still can't quite get my head around the way that the logical or works. I would think that it would only return 1 or 0, but evidently here it's returning the whole string back again.

      Anyway, thanks for all the help everybody. I did kinda post problem Y without getting into the specifics of problem X, but that was only because I thought X would bore everybody to tears!
        Just be careful about $description{foo} that evaluate to false:
        use strict; use warnings; my %description = ( a => 'a vowel', b => 'a consonant', 0 => '0', default => 'not in the alphabet' ); for my $char (qw/a b c 0/) { my $d = $description{$char} || $description{default}; print("$char is $d\n"); }
        tford:

        perldoc perlop yields:

        C-style Logical Or

        Binary "||" performs a short-circuit logical OR operation. That is, if the left operand is true, the right operand is not even evaluated. Scalar or list context propagates down to the right operand if it is evaluated.

        The "||" and "&&" operators return the last value evaluated (unlike C's "||" and "&&", which return 0 or 1). Thus, a reasonably portable way to find out the home directory might be:

        $home = $ENV{'HOME'} || $ENV{'LOGDIR'} || (getpwuid($<))[7] || die "You're homeless!\n";
        ...roboticus
Re: Default Hash Key
by TGI (Parson) on May 02, 2008 at 00:57 UTC

    Use a tied hash.

    package DefaultHash; sub TIEHASH { my $class = shift; my $default = shift; my $self; $self->{_DEFAULT} = $default; $self->{_HASH} = {}; return bless $self, $class; } sub FETCH { my $self = shift; my $key = shift; return exists $self->{_HASH}{$key} ? $self->{_HASH}{$key} : $self->{_DEFAULT} } sub EXISTS { my $self = shift; my $key = shift; return exists $self->{_HASH}{$key}; } sub FIRSTKEY { my $self = shift; my $foo = keys %{$self->{_HASH}}; # Reset each return each %{$self->{_HASH}}; } sub NEXTKEY { my $self = shift; return each %{$self->{_HASH}}; } sub CLEAR { my $self = shift; $self->{_HASH} = {}; } sub DELETE { my $self = shift; my $key = shift; return delete $self->{_HASH}{$key}; } sub STORE { my $self = shift; my $key = shift; my $value = shift; $self->{_HASH}{$key} = $value; } package main; tie( my %hash, 'DefaultHash', 'a default value' ); %hash = ( a => 1, b => 2, c => 3 ); print "$_ => $hash{$_}\n" for qw(a b c d);


    TGI says moo

      That's way way longer than it needs to be (and thus more error-prone and harder to maintain).

        Thanks for the pointer to Tie::ExtraHash.

        I wonder how much of the "new" goodness added in Perl 5.8 I still haven't found...


        TGI says moo

Re: Default Hash Key
by wade (Pilgrim) on May 02, 2008 at 00:32 UTC
    You could always do something like:
    my $foo = exists($description{$input}) ? $description{$input} : "default value";
    Or the two-step approach:
    my $foo = $description{$input}; $foo = "default value" if (!defined($foo));
    --
    Wade

      Using the "//" operator, this folds into:

      my $foo = $description{$input} // "default value";
Re: Default Hash Key
by toolic (Bishop) on May 02, 2008 at 00:52 UTC
    Is there a way to define a default value for a hash?
    I do not know. But, you could create a function to see if a key exists:
    use warnings; use strict; my %description = ( a => 'a vowel', b => 'a consonant' ); print 'a is ', check(\%description, 'a'), "\n"; print 'b is ', check(\%description, 'b'), "\n"; print '~ is ', check(\%description, '~'), "\n"; sub check { my ($href, $key) = @_; return (exists $href->{$key}) ? $href->{$key} : 'not in the alphab +et'; }

    prints:

    a is a vowel b is a consonant ~ is not in the alphabet
Re: Default Hash Key
by Anonymous Monk on May 07, 2008 at 02:09 UTC

    In this case I would simply opt for “keep it simple.” Write a nice short sub that produces the effect that you want ... and move along. There is no problem with doing an “if exists” test for a particular value followed by an apparent-lookup of that same value, because you can be quite sure that on that “second lookup” that value will be found most-immediately.

    So, just go for an implementation that is reasonably clear, and trust the Gods of Perl to have done the rest on your behalf. (They're quite good at that sort of thing...)