http://qs1969.pair.com?node_id=488554

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

Hello,

First off, I am really embarressed to be posting something that I thought would be so easy. I'm trying to parse filenames into hash trees. I'm humbled that I can't make this work. Arg.

#!/usr/bin/perl -w use strict; use Data::Dumper; $Data::Dumper::Deepcopy=1; $Data::Dumper::Purity=1; $Data::Dumper::Sortkeys=1; my @filenames = <DATA>; chomp @filenames; my $files = (); foreach my $file (@filenames) { print "processing file: $file\n"; foreach my $part (reverse split(/[-_.]/,$file)) { print "processing part: $part\n"; # $files = \{$part}; # nope # $files = \$files->{$part}; # nope # $files = \${$files->{$part}}; # nope # $files = \%{$files->{$part}}; # nope # $files = $files->{$part}; # nope # $files = {$files->{$part}}; # nope # $files = {$files->{$part}}; # nope # $files = ($files->{$part}); # nope # $files = \($files->{$part}); # nope # $files = ${$files->{$part}}; # nope # $files = {$part => $files}; # nope $files->{$part} = \$files; } } print "\n\nit looks like this:\n"; print Dumper($files); print "\n\nBut I want it to end up like this:\n"; my $test; $test->{'0001'}{test}{thing}{sgi} = 1; $test->{'1'}{tif} = 1; $test->{'2'}{tif} = 1; $test->{'10'}{tif} = 1; $test->{final}{'0001'}{tif} = 1; $test->{final}{'0002'}{tif} = 1; $test->{final}{'0003'}{tif} = 1; $test->{final}{'0004'}{tif} = 1; # etc.... print Dumper($test); __DATA__ 0001-test-thing.sgi 1.tif 10.tif 2.tif final-0002.tif final-0003.tif final-0004.tif final-0001.tif

reluctantly resigned,
blahblah

Replies are listed 'Best First'.
Re: delimited strings into a hash of hashes
by ikegami (Patriarch) on Sep 02, 2005 at 08:01 UTC

    Your problem is that you're thinking you need a HoH and you create a HoH, but what you want is a HoHoHo... of indefinite depth. That means you have to loop through the levels of the hash. pg showed you a way of doing that. Here are three more.

    Easier to read/understand alternative:

    foreach my $file (@filenames) { my $h = \%files; my @parts = split(/[-_.]/,$file); my $last = pop(@parts); foreach (@parts) { $h->{$_} ||= {}; $h = $h->{$_}; } $h->{$last} = 1; }

    Shorter alternative:

    foreach my $file (@filenames) { my $p = \\%files; my @parts = split(/[-_.]/,$file); $p = \(${$p}->{$_}) foreach @parts; ${$p} = 1; }

    Even shorter, but it's the same algorithm:

    foreach (@filenames) { my $p = \\%files; $p = \(${$p}->{$_}) foreach split /[-_.]/; ${$p} = 1; }

    Question:

    Have you considered the case where you have the files "example.txt" and "example.txt.bak"? Currently,
    If you get the longer one first, the longer one will be erased.
    If you get the shorter one first, the program will die.

Re: delimited strings into a hash of hashes
by pg (Canon) on Sep 02, 2005 at 05:56 UTC

    I tried to keep it similar to your original logic:

    use strict; use warnings; use Data::Dumper; my @filenames = <DATA>; chomp @filenames; my %files; foreach my $file (@filenames) { my $last = \%files; my @parts = split(/[-_.]/,$file); for my $i (0 .. $#parts) { $last->{$parts[$i]} = (($i != $#parts) ? {} : 1) if (!exists($ +last->{$parts[$i]})); $last = $last->{$parts[$i]}; } } print Dumper(\%files); my $test; $test->{'0001'}{test}{thing}{sgi} = 1; $test->{'1'}{tif} = 1; $test->{'2'}{tif} = 1; $test->{'10'}{tif} = 1; $test->{final}{'0001'}{tif} = 1; $test->{final}{'0002'}{tif} = 1; $test->{final}{'0003'}{tif} = 1; $test->{final}{'0004'}{tif} = 1; print Dumper($test); __DATA__ 0001-test-thing.sgi 1.tif 10.tif 2.tif final-0002.tif final-0003.tif final-0004.tif final-0001.tif
Re: delimited strings into a hash of hashes
by grinder (Bishop) on Sep 02, 2005 at 17:52 UTC

    This should do the trick:

    my $r; ... while( <> ) { chomp; $r = insert($r, split /[-_.]/, $_); } sub insert { my $r = shift; my $last = \$r; $last = \$$last->{$_} for @_; $$last ||= 1; $r; }

    edit: fixed up call to insert()

    - another intruder with the mooring in the heart of the Perl

Re: delimited strings into a hash of hashes (Dive)
by tye (Sage) on Sep 03, 2005 at 05:08 UTC

    Cool. This required a nice enhancement to Data::Diver. Thanks.

    #!/usr/bin/perl -w use strict; use Data::Diver 1.01 qw( DiveVal ); my @filenames = <DATA>; chomp @filenames; my $files = { }; foreach my $file ( @filenames ) { print "processing file: $file\n"; my @parts= split /[-_.]/, $file; DiveVal( $files, \(@parts) )= 1; } __DATA__ 0001-test-thing.sgi 1.tif 10.tif 2.tif final-0002.tif final-0003.tif final-0004.tif final-0001.tif

    Sorry, the new version of the module will take a few hours / days to get to all the CPAN mirrors.

    - tye