http://qs321.pair.com?node_id=322837

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

I'm tinkering with my personal website and have a CGI which reads in a real system directory structure to represent categories etc. The resulting hash represents the directory structure plus stores a small amount of info for particular files.

The structure looks like this as a cut-down example, it could be nested to any level, not just what is shown:

$VAR1 = { 'misc' => {}, 'docs' => { 'howtos' => { 'email' => { 'index.html' => { 'date' => '21-1-2004', 'size' => '691' } }, 'ftp' => {}, 'ssh' => {} } } };

What I want to do is directly reference one of the nested hashes based on a single string containing the real system path. If I had the string: "/docs/howtos/email/" I'd want to produce the contents of: $VAR1->{docs}->{howtos}->{email}

I came up with a seemingly dodgy way with eval as follows (this is the first time I've used eval before BTW):

# yes, I realise there is NO error checking :) # assume $tree contains data structure shown above # assume input = "/docs/howtos/email/" my $string = join('}->{',split(/\//,$input)); $string =~ s/^.(.+)/$1}/; $string = '$tree'.$string."->{'index.html'}->{size};"; # string now contains: "$tree->{docs}->{howtos}->{email}->{'index.html +'}->{size};" # constructed reference print eval $string; # explicit reference print $tree->{docs}->{howtos}->{email}->{'index.html'}->{size};

This outputs the same for both cases and does what I want - but it seems like a hack. I was curious as to how others would do it, and most probably how I *should* do it? :)

Thanks,

Ryan

Replies are listed 'Best First'.
Re: Building dynamic nested hash references
by broquaint (Abbot) on Jan 21, 2004 at 11:15 UTC
    Yes, it's generally best to avoid unnecessary eval STRING, so a simple iteration over the result of spliting the string should do it e.g
    my $h = { 'misc' => {}, 'docs' => { 'howtos' => { 'email' => { 'index.html' => { 'date' => '21-1-2004', 'size' => '691' } }, 'ftp' => {}, 'ssh' => {} } } }; use Data::Dumper; print Dumper( find_hash($h,"/docs/howtos/email/") ); sub find_hash { my($tree, $path) = @_; my $n = $tree; for(grep length, split '/', $path) { return unless exists $n->{$_} and defined $n->{$_}; $n = $n->{$_}; } return $n; } __output__ $VAR1 = { 'index.html' => { 'date' => '21-1-2004', 'size' => '691' } };
    That could probably do with a bit more data validation but it should illustrate, roughly, a saner approach to iterating through your nested hash.
    HTH

    _________
    broquaint

Re: Building dynamic nested hash references
by ysth (Canon) on Jan 21, 2004 at 11:14 UTC
    my $var; my @parts = grep length, split m#/#, $input; for ($var = $VAR1; @parts && $var; $var = $var->{shift @parts}) {} use Data::Dumper; print Dumper $var;
    is how I'd do it. This is one of the few situations in which I feel strangely drawn to the c-style for loop.
Re: Building dynamic nested hash references
by BrowserUk (Patriarch) on Jan 21, 2004 at 12:08 UTC

    A short function to follow the path makes for a nice clean syntax.

    #! perl -slw use strict; my $tree = { 'misc' => {}, 'docs' => { 'howtos' => { 'email' => { 'index.html' => { 'date' => '21-1-2004', 'size' => '691' } }, 'ftp' => {}, 'ssh' => {} } } }; sub find{ my( $href, $path ) = @_; $href = $href->{ $_ } for grep{ $_ } split '/', $path; $href; } my $input = '/docs/howtos/email/'; print find( $tree, $input )->{ 'index.html' }{ size };

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Timing (and a little luck) are everything!

Re: Building dynamic nested hash references with map
by l3nz (Friar) on Jan 21, 2004 at 12:15 UTC
    This version is slightly more compact by using map to navigate through the tree. A wrong path will return undef. I use the grep because so you can add empty "/" at the beginning or end of the filename, at your will.
    use Data::Dumper; $VAR1 = { 'misc' => {}, 'docs' => { 'howtos' => { 'email' => { 'index.html' => { 'date' => '21-1-2004', 'size' => '691' } }, 'ftp' => {}, 'ssh' => {} } } }; $fs = "/docs/howtos/email/"; $v = $VAR1; # this is the actual processing map { $v = $v->{$_} } grep { length > 0 } split m./., $fs ; print Dumper $v;
    Enjoy!

    Note. I have been reading Paul Graham's On Lisp lately, does it show? :)

    Note: I submitted this comment before seeing's BrowserUk's, looks like we had the same idea.

Re: Building dynamic nested hash references
by ryan (Pilgrim) on Jan 21, 2004 at 12:05 UTC
    Thanks ysth and broquaint, that does it nicely.

    Now I know this follow-on question is really asking for another data structure like a linked list, but ...

    ... assuming I had the same information but wanted to step my way back *up* the 'tree' to find the parent hash of the item we found in this example, and then the parent of that etc, is there any way to do this on the hash I've displayed? The only way I can think of is to store references to each of the steps taken down the tree (when the item was initially found) in an array and query that?

    Update: or storing parent hash information in the tree itself as it is built?

    I really want a different data structure don't I? :)

    Ryan

      Like the actual filesystem, you should store a ".." entry in each directory when you create it.

      The PerlMonk tr/// Advocate