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

Hello Monks:

Here is an interesting case I don't see covered in Perldoc. I thought about changing my data structure so it doesn't happen, but wanted to post it here first in hopes of attaining Wisdom and sharing a fun puzzle. Apologies if I'm missing something in the FAQ.

I start with a reference to a hash value and want to get at its key. Suppose that I start with a basic hash:
%The_Hash = ( "first_key" => 1, "second_key" => 2 );

Now, I make a reference to one of the hash values:
$Ref_To_Hash_Value = \$The_Hash{"second_key"}; print ${$Ref_To_Hash_Value}; # prints 2

Given $Ref_To_Hash_Value, I now need to obtain the name of its key. In other words, I need the string "second_key".

Now the fun part . . . assume that %The_Hash will become very large, that it will be changing frequently, and that we'll create lots and lots of references to its values. We'll also want to do this key lookup quite often. We _could_ iterate over the whole hash using keys() or reverse(), but that would require traversing the whole darn hash every time we want to find a key. That could get pretty slow. It seems to me that, given a reference to the value of the key/value pair, we should be able to pull some sort of syntax magic to get the key.

Here are two ideas, neither of which I know how to implement:
* Decrement the internal pointer of the hash, since the key should be the element before the value, right?
* Start with a reference to the key rather than the value, then deref it to retrieve the string.

Thanks in advance for any Wisdom you might be able to offer.

Gazpacho (Initiate, but learning)

Replies are listed 'Best First'.
Re: Given ref to hash value, retrieve the key
by Abigail-II (Bishop) on May 22, 2003 at 22:48 UTC
    You can't do that. You're best solution is to use a different datastructure.

    About your ideas:

    • Perl isn't C. Perl doesn't have pointers, so you can't decrement a pointer. And you cannot get to the pointers at the C level, unless you write in XS (or Inline::C). But internally, the hash isn't stored as a single array with alternating keys and values. The structure is far more complex.
    • You cannot take the reference of a hash key. Hash keys aren't normal values.

    Abigail

Re: Given ref to hash value, retrieve the key
by Limbic~Region (Chancellor) on May 22, 2003 at 23:58 UTC
    gazpacho,
    What you are asking for isn't what you are asking for ;-)

    You want a reference, which is small, light weight, and can easily be thrown around to yield a hash key when used one way and yield that specific hash key's value used a different way.

    As has been stated, this can't be done the way you are trying to do it, but you could do it with a closure. This would work - but it would be nasty (not elegant) looking code. It won't provide the efficiency of just the ref to the value, but it is extremely fast in comparison to iteration and it allows for duplicate values unlike a reverse hash. Here it is:

    #!/usr/bin/perl -w use strict; my %hash = ('first_key' => 1 , 'second_key' => 2); my $magic_ref = Create_Magic(\$hash{'second_key'},'second_key'); print "My value is ", ${&$magic_ref} , "\n"; print "My key is ", &$magic_ref('key') , "\n"; sub Create_Magic { my ($value, $key) = @_; return sub { if ($_[0] && $_[0] eq 'key') { return $key; } $value; } }

    This creates a reference that yields the key when used one way and the value when used another way. The important thing here is that it is a code reference not a scalar reference. As other monks and I have already stated, doing it the way you wanted to isn't possible.

    Cheers - L~R

Re: Given ref to hash value, retrieve the key
by BrowserUk (Patriarch) on May 22, 2003 at 23:40 UTC

    If your data is in a hash, why take a reference to the value? Why not store the key wherever your using the reference? That way you have the key when you need it and can get the value with a fast lookup.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
Re: Given ref to hash value, retrieve the key
by Ovid (Cardinal) on May 22, 2003 at 22:42 UTC

    I can think of two ways to accomplish this. Either might be appropriate, depending upon what you want to do. The second method is probably cleaner if you can assemble the entire hash at once and do a single reversal on it.

    #!/usr/bin/perl -w use strict; my %hash = qw( foo 1 bar 1 baz 1 ); my $ref = \$hash{bar}; my ($key) = grep { \$hash{$_} eq $ref } keys %hash; print "$key\n"; # or my %ref_hash = map { \$hash{$_}, $_ } keys %hash; print $ref_hash{$ref};

    With either of these solutions, though, I suspect that you likely have a design issue. Can you step back and explain the larger problem? We can probably come up with a better way of accomplishing what you need.

    Cheers,
    Ovid

    New address of my CGI Course.
    Silence is Evil (feel free to copy and distribute widely - note copyright text)

      The OP already pointed out that iterating over the hash was a solution, but also dismissed it for efficiency reasons. And rightly so.

      Abigail

        Thanks. I missed that. I idly wonder if a tied hash would be a wee bit faster, but it would still be an awful solution.

        Cheers,
        Ovid

        New address of my CGI Course.
        Silence is Evil (feel free to copy and distribute widely - note copyright text)

Re: Given ref to hash value, retrieve the key
by thelenm (Vicar) on May 22, 2003 at 23:16 UTC
    When I've had to do this before and knew for sure that there was a one-to-one mapping between keys and values (i.e., no duplicate values), I've just created two hashes (or a single hash with two levels) and accessed them using subroutines or an object interface. Something like this:
    my (%key_to_value, %value_to_key); sub add_key_value { my ($key, $value) = @_; $key_to_value{$key} = $value; $value_to_key{$value} = $key; }
    and so forth with more subroutines for deleting and changing mappings. Of course, this solution should use twice as much space as the original hash, and requires you to remember to use the subroutines whenever changing any of your data. You may be able to get around the second problem with a clever tied interface. Quick search of CPAN... In fact, it looks like Tie::Hash::TwoWay may do just this. Good luck!

    -- Mike

    --
    just,my${.02}

Re: Given ref to hash value, retrieve the key
by theorbtwo (Prior) on May 22, 2003 at 23:05 UTC

    You could pass in \%hash, $key, and deref as approprate. Or, you could do what somebody already suggested and tell us what the problem is. (Oh, and note that

    my(%hash, $ref); $ref=\$hash{key}; $$ref=42; print $hash{key};
    will print 42.


    Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

Re: Given ref to hash value, retrieve the key
by pzbagel (Chaplain) on May 23, 2003 at 00:38 UTC

    Okay, this is NOT production ready, or even sane for that matter, but I couldn't resist. I call it: "hashes2hashes.pl". The script creates a hash of key=>value pairs from the __DATA__. While it is adding to the hash, it creates an array of references to the data. It then uses the references as keys to another hash which stores the $key of the first hash as the data element. Now if you have a reference to a value in %hash1 you can use it to look up the corresponding key stored in %hash2.

    #!/usr/bin/perl use Data::Dumper; while(<DATA>){ chomp; ($key,$val)=split/=/; #add key => value pair to %hash1 $hash1{$key}=$val; #add reference to $hash1{$key} to @refs push @refs,\$hash1{$key}; #create second %hash that stores the reference as a key and $key as th +e value $hash2{\$hash1{$key}}=$key; } #Now let's see how we can reference the various things. print "Reference \$refs[0] is the value $refs[0] and points to: ${$ref +s[0]}\n"; print "In \$hash2, $refs[0] points to the value $hash2{$refs[0]}\n"; print "The key $hash2{$refs[0]} can be then used to reference \$hash1{ +\$hash2{\$refs[0]}} => $hash1{$hash2{$refs[0]}}\n"; # And some Dumper output to make sure it looks good. print Dumper(\%hash1, \@refs, \%hash2); __DATA__ key1=1 key2=2 key3=3 OUTPUT: Reference $refs[0] is the value SCALAR(0x804c05c) and points to: 1 In $hash2, SCALAR(0x804c05c) points to the value key1 The key key1 can be then used to reference $hash1{$hash2{$refs[0]}} => + 1 $VAR1 = { 'key2' => '2', 'key1' => '1', 'key3' => '3' }; $VAR2 = [ \$VAR1->{'key1'}, \$VAR1->{'key2'}, \$VAR1->{'key3'} ]; $VAR3 = { 'SCALAR(0x804c05c)' => 'key1', 'SCALAR(0x804c194)' => 'key3', 'SCALAR(0x804c140)' => 'key2' };

    I remember reading that using references as hash keys was bad. Maybe someone who knows a little more perl internals can tell me if the references would ever change during my program(provided I don't copy %hash1 of course)? Or if there is some other horrid reason not to do things this way?

      The reason that using references as hash keys are bad is
      that the references are 'Stringified' in the hash thus:

      my $val = 1; my %hash = ( \$val => 2 ); foreach my $ref (keys %hash) { print $$ref, "\n"; }

      Does not yield the value 1, instead you get undef.

Re: Given ref to hash value, retrieve the key
by gazpacho (Acolyte) on May 23, 2003 at 05:01 UTC
    Hello all around and thank you for your comments. Several contain excellent suggestions, and some make the larger (and correct) point that I'm barking up the wrong tree. Time to re-think the data structure.

      Your initial request immediately made me think of the lisp function called rassq, which operates on an alist -- which is very similar to a hash but implemented differently; because of the way Perl's hashes are implemented, forward lookups are very fast, but reverse lookups are relatively expensive (as you've discovered). With alists, forward and reverse lookups are roughly equally expensive.

      It is relatively easy to implement an alist in Perl. (The following code is untested...

      my @alist = ( [key1 => value1], [key2 => value2], [key3 => value3], ); sub assq { my ($alistref, $key) = @_; for (@$alistref) { return $_ if $$_[0] eq $key; # eq doesn't mean exactly the same thing as in lisp, # but don't worry about that if you don't know lisp. } } sub rassq { my ($alistref, $value) = @_; for (@$alistref) { return $_ if $$_[1] eq $value; # eq doesn't mean exactly the same thing as in lisp, # but don't worry about that if you don't know lisp. } }

      If you are working with large alists, assq will be much slower than the equivalent hash lookup. You probably can improve it slightly be replacing foreach with an ugly C-style for loop.

      I also don't know whether rassq will be significantly faster than doing the equivalent thing on a hash; what I do know is that it will be just as fast as assq. If you want to compare the alist rassq with the equivalent reverse hash lookup, I suggest using Benchmark. Here's the closest thing I can come up with for doing a rassq-like operation on a hash:

      sub hashrassq { my ($hashref, $value) = @_; foreach (keys %$hashref) { return $_ if $$hashref{$_} eq $value; } }

      Also note that there are significant differences if more than one hash key has the same value; the alist implementation will always return the first one as defined in terms of the order in which the associations were added to the list (unless you add by unshifting or similar), but the hashrassq will pick one (for practical purposes) arbitrarily. You can define an order by sorting the key list for the foreach loop, but this does not allow you to take the first one added to the hash, since hashes don't store order like that. Also note that the hash implementation may be more efficient if you iterate over the hash using each, just as the alist may be more efficient if you iterate by index. Only way to be sure is to use Benchmark.