tags:

views:

75

answers:

5

Let's assume that I have a complex hash reference $hash_ref, and I would like to access data in it by doing something like this:

my $string1 = "{books}";
my $string2 = "{31335}->{book_name}";
print Dumper($hash_ref->$string1->$string2);

Of course, this doesn't work, but I hope it explains what I'd like to do.

Obviously, there are many ways I can make this work, but I am (out of curiosity) really interested to figure out if there is some Perl magic that could make this work without splitting strings, etc.

I know that I could create 3 strings ("books", "31335", "book_name") and have this done in a second, and there are certainly other ways, but I never understood if one could actually access hash data by using strings that represent hash structure, like in the above example.

Thanks :)

A: 

AFAIK there is no already available thing which can do this,you have to write a wrap up code to make it possible.I think hash implementation and functions are really simple and cool,you can make it work in very less code.

Anil Vishnoi
+6  A: 

It can be done using eval. However, just because some can be done doesn't mean it should.

use strict;
use warnings;

my $hr = { books => { 31335 => { book_name => 'FOO' } } };

my $k1 = "{books}";
my $k2 = "{31335}->{book_name}";

my $f = eval "\$hr->$k1->$k2";  # Don't do this. It's a terrible idea.
print $f, "\n";                 # FOO

You should bite the bullet and extract the keys from the strings:

my @ks = "$k1$k2" =~ /\{ \s* (.+?) \s* \}/gx;
$f = $hr;
$f = $f->{$_} for @ks;
print $f, "\n";                 # FOO
FM
Thank you very much for your answer. I appreciate other answers too, but this one really answered everything I wanted to know. I didn't think it was possible, but I forgot about `eval` completely. Now I know that it can be done (not that I'll be doing it, though :).Thanks once again.
sentinel
A: 
my $string1 = "{books}";
my $string2 = "{31335}->{book_name}";
my $hash_ref = { key1 => { books =>  { 31335 =>  { book_name => "gold" }}}};
my $hash_ref_name = "\$hash_ref";
my $str = join("->", $hash_ref_name, "{key1}", $string1, $string2);
print eval $str, "\n"
wecac
+1  A: 

I am in no way saying that this is a good idea, but here is how to do it (without eval):

use strict;
use warnings;

my $hash_ref = {books => {31335 => {book_name => 'perl'}}};

my $key = sub {
    my $hash = shift;
    my @keys = grep {s!^\{|\}$!!g; $_} split /->/ => "@_";
    $hash = $$hash{$_} for @keys;
    $hash
};

my $string1 = "{books}";
my $string2 = "{31335}->{book_name}";

print $hash_ref->$key($string1)->$key($string2);  # prints 'perl'

or, to keep the calling code a little cleaner, you can code up a boxing class to handle arbitrary strings as method calls:

sub key_methods {bless \$_[0] => 'KeyMethods'}

{package KeyMethods;
    use overload nomethod => sub {${$_[0]}},  # automatic unboxing
                    '%{}' => sub {${$_[0]}};
    sub AUTOLOAD {
        my $ret = ${$_[0]}->$key(our $AUTOLOAD =~ /([^:]+)$/);

        ref $ret eq 'HASH'
            ? bless \$ret
            : $ret;
    }
}

print key_methods($hash_ref)->$string1->$string2;  # prints 'perl'
Eric Strom
Really appreciate this answer. I just need to decrypt it first, but I'm getting there :)Thanks once again.
sentinel
@sentinel => is there any particular part you would like explained?
Eric Strom
A: 

If you are more interested in having a variable store a 'path' to use in accessing a data structure, rather than being able to use a string that can be user submitted or dynamically generated, then you can use an lvalue anonymous sub.

my $deref1 = sub :lvalue { $_[0]->{books} };
my $deref2 = sub :lvalue { shift->{31335}{book_name} }; # if you don't like $_[0]

my $hash_ref = { };

# :lvalue on the sub allow it to be assigned to
$hash_ref->$deref1 = { 31335 => { book_name => 'FOO' } };

print $hash_ref->$deref1->$deref2, "\n";
Ven'Tatsu