perlhash-of-hasheshashrefarrayref

Perl: Create hash of hashes, last key as a reference to an array


http://codepad.org/8fJG5XaB

Need a little help creating hashrefs of hashrefs, with the last key as a reference to an array.

use Data::Dumper;

   my $foo = "a:b:c:d:a";
   my $bar = "a:b:c:d:z";
   my $hoh = {};

   sub createHash {

      my ($hoh,$orig,$rest,$last) = @_;
      $rest = $rest || $orig;
      $_    = $rest;

      if (/^(.*?):(.*)$/) { 
         $hoh->{$1} = $hoh->{$1} || {};
         createHash($hoh->{$1},$orig,$2,$1);
      }
      elsif (defined($last)) {
         push (@{$hoh->{value}} , [$rest,$orig]);
      }

      return $hoh;
   }

   $hoh = createHash($hoh,$foo,undef);
   $hoh = createHash($hoh,$bar,undef);

   print Dumper($hoh);

What's Wanted:

$VAR1 = {
          'a' => {
                   'b' => {
                            'c' => {
                                     'd' => [
                                               [
                                                 'a',
                                                 'a:b:c:d:a'
                                               ],
                                               [
                                                 'z',
                                                 'a:b:c:d:z'
                                               ]
                                            ]
                                   }
                          }
                 }
        };

You can compare this with the output from codepad. Notice the subtle difference; instead of 'd' being a hashref that has an arrayref value, 'd' is the arrayref and there is no value.


Solution

  • I'd suggest Data::Diver, though it is a bit awkward since it wants to always create scalar references at the end, and that's not what we want. Thus, I cheat a bit.

    The main thing here is that we can save effort (mostly in maintenance) by deciphering all the keys at once, and using a while loop (inside Data::Diver) instead of recursion, which is, by its nature, a bit more fun to decipher :-) Combine that with the fact that even if it were recursion, it'd be hidden in a nice, neat function call, it's a double win :-)

    use Data::Dumper;
    use Data::Diver qw(DiveRef);
    
    my $foo = "a:b:c:d:a";
    my $bar = "a:b:c:d:z";
    my $hoh = {};
    
    sub add_item
    {
        my $href = shift;
        my $str  = shift;
    
        my @keys = split /:/, $str;
    
        # force an array to be autovivified if it isn't already there.
        # (this is kinda cheating)
        my $cheat  = DiveRef($href, @keys[0..$#keys-1], 0);
        my $ref = DiveRef($href, @keys[0..$#keys-1]);
    
        # if we cheated (thus $$cheat will be undef), we need to pop that
        # off.
        pop @$$ref unless $$cheat;
    
        # store this at the end.
        push @{$$ref}, [ $keys[-1], $str ];
    
        return;
    }
    
    add_item($hoh, $foo);
    add_item($hoh, $bar);
    print Dumper($hoh);
    

    Hope that helps,

    UPDATE: After conversing with tye, he provided a more concise way to do this. It uses Data::Diver still, but has a much simpler workaround embedded. (His claim is that perl has a bug here with :lvalue subs and push - I don't know better, so I'll take his word.)

    use Data::Dumper;
    use Data::Diver qw(DiveRef DiveVal);
    
    my $foo = "a:b:c:d:a";
    my $bar = "a:b:c:d:z";
    my $hoh = {};
    
    sub add_item
    {
        my $href = shift;
        my $str  = shift;
    
        my @keys= split /:/, $str;
        my $last= pop @keys;
        push @{ DiveVal( $href, \( @keys ) ) ||= []}, [ $last, $str ];
    
    
        return;
    }
    
    add_item($hoh, $foo);
    add_item($hoh, $bar);
    print Dumper($hoh);