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 | [reply] |
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 | [reply] [d/l] |
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
| [reply] |
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) | [reply] [d/l] |
|
|
The OP already pointed out that iterating over the hash was
a solution, but also dismissed it for efficiency reasons.
And rightly so.
Abigail
| [reply] |
|
|
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)
| [reply] |
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} | [reply] [d/l] |
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).
| [reply] [d/l] |
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? | [reply] [d/l] |
|
|
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.
| [reply] [d/l] |
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.
| [reply] |
|
|
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.
| [reply] [d/l] [select] |