Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically/recursively building hashes in Perl?

I'm quite new to Perl and I'm trying to build a hash recursively and getting nowhere. I tried searching for tutorials to dynamically build hashes, but all I could find were introductory articles about hashes. I would be grateful if you point me towards the right direction or suggest a nice article/tutorial.

I'm trying to read from a file which has paths in the form of

one/two/three
four
five/six/seven/eight

and I want to build a hash like

VAR = {
    one : {
        two : {
            three : ""
        }
    }
    four : ""
    five : {
        six : {
            seven : {
                 eight : ""
            }
        }
    }
}

The script I'm using currently is :

my $finalhash = {}; 
my @input = <>;

sub constructHash {
    my ($hashrf, $line) = @_; 
    @elements = split(/\//, $line);
    if(@elements > 1) {
        $hashrf->{shift @elements} = constructHash($hashrf->{$elements[0]}, @elements ); 
    } else {
        $hashrf->{shift @elements} = ""; 
    }
    return $hashrf;
}

foreach $lines (@input) {
    $finalhash = constructHash($finalhash, $lines);
}
like image 479
Gaurav Dadhania Avatar asked Dec 30 '10 00:12

Gaurav Dadhania


3 Answers

Data::Diver covers this niche so well that people shouldn't reinvent the wheel.

use strict;
use warnings;
use Data::Diver 'DiveVal';
use Data::Dumper;

my $root = {};
while ( my $line = <DATA> ) {
    chomp($line);
    DiveVal( $root, split m!/!, $line ) = '';
}
print Dumper $root;
__DATA__
one/two/three
four
five/six/seven/eight
like image 199
ysth Avatar answered Oct 19 '22 16:10

ysth


This is a bit far-fetched, but it works:

sub insert {
  my ($ref, $head, @tail) = @_;
  if ( @tail ) { insert( \%{$ref->{$head}}, @tail ) }
  else         {            $ref->{$head} = ''      }
}

my %hash;
chomp and insert \%hash, split( '/', $_ ) while <>;

It relies on autovivification, which is admittedly a bit advanced for a beginner.

What would probably make any answer to your question a bit twisted is that you ask for empty strings in the leaves, which is of a different "type" than the hashes of the nodes, and requires a different dereferencing operation.

like image 37
JB. Avatar answered Oct 19 '22 16:10

JB.


I've never done something like this, so this approach is likely to be wrong, but well, here's my shot:

use 5.013;
use warnings;
use Data::Dumper;

sub construct {
   my $hash = shift;
   return unless @_;

   return construct($hash->{shift()} //= {}, @_);
}

my %hash;

while (<DATA>) {
   chomp;
   construct(\%hash, split m!/!);
}

say Dumper \%hash;

__DATA__
one/two/three
four
five/six/seven/eight

EDIT: Fixed!

EDIT2: A (I think) tail-call optimized version, because!

sub construct {
   my $hash = shift;
   return unless @_;
   unshift @_, $hash->{shift()} //=  @_ ? {} : '';

   goto &construct;
}
like image 31
Hugmeir Avatar answered Oct 19 '22 14:10

Hugmeir