开发者

Access hash reference data using strings representing the hash structure

开发者 https://www.devze.com 2023-01-13 15:37 出处:网络
Let\'s assume that I have a complex hash reference $hash_ref, and I would like to access data in it by doin开发者_开发百科g something like this:

Let's assume that I have a complex hash reference $hash_ref, and I would like to access data in it by doin开发者_开发百科g 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 :)


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


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'


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";


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"


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消