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

I'm having one of those days...

I'm trying to build a hierarchical hash that reflects a string syntax that represents parent-child relationships. E.g.:

my $str = "One:Two:Three:four, One:Two:3:4, One:2:3:4, One:2:three";

should result in

$keywords = { One => { Two => { Three => four, 3 => 4 }, 2 => { 3 => 4, three => "" } } };

Though it should go without explanation, the idea is that "One" is the parent node to everything. It has two children, "Two" and "2". Each of those has children, who are either leaves or have their own children....

My current implementation is to split each comma-delimeted token along the :'s, and then call itself recursively to build the hash. I'm sooooo close, but it's just not getting it. I don't want to post my code so far so as to avoid influencing a better, more efficient method... you know how it goes: you work on something too long, and you can't see the forest for the trees anymore, and you doubt whether you went about it right in the first place.

The routine to test whether it works is here:

my $n = 0; sub print_keywords { my $k = shift; for my $key (keys %$k) { print " " x $n; print "$key\n"; $n++; if (keys %{$k->{$key}}) { print_keywords($k->{$key}); } elsif ($k->{$key}) { print " " x $n, $k->{$key}, "\n"; } $n--; } }

And, of course, you call it with the top of the hash returned after the parsing is done:

print_keywords($keywords);

Replies are listed 'Best First'.
Re: parsing a parent-child syntax to make a hash
by Fletch (Bishop) on Jun 08, 2007 at 17:56 UTC

    If your requirements are flexible enough (or you can deal with translating to and/or from its syntax) maybe Data::Path might be of use?

Re: parsing a parent-child syntax to make a hash
by ikegami (Patriarch) on Jun 08, 2007 at 18:46 UTC

    First of all, you can easily dump structures using Data::Dumper. No need to roll out your custom subroutine.

    You can do some complicated dereferencing/referencing:

    use strict; use warnings; my $str = "One:Two:Three:four, One:Two:3:4, One:2:3:4, One:2:three"; my %hash; my @defs = split(/\s*,\s*/, $str); # Use Text::CSV? foreach my $def (@defs) { my @keys = split(/:/, $def); my $val = pop(@keys); my $p = \\%hash; while (@keys) { $p = \($$p->{shift(@keys)}); } $$p = $val; } require Data::Dumper; print(Data::Dumper::Dumper(\%hash));

    Or you can use tye's Data::Diver module:

    use strict; use warnings; use Data::Diver qw( DiveVal ); my $str = "One:Two:Three:four, One:Two:3:4, One:2:3:4, One:2:three"; my %hash; my @defs = split(/\s*,\s*/, $str); # Use Text::CSV? foreach my $def (@defs) { my @keys = split(/:/, $def); my $val = pop(@keys); DiveVal(\%hash, map { \$_ } @keys) = $val; } require Data::Dumper; print(Data::Dumper::Dumper(\%hash));

    The second snippet is untested.

    Important! The first snippet doesn't check for bad data such as 'one:orange, one:apple:car', but it would be easy to add a check. I don't know how Data::Diver deals with that.