in reply to Expanding / flattening a structure

That is a great problem i.e. I found it a lot more difficult than it first appeared. I'm not sure I'd call this solution elegant, although it would look better if the code is cleaned up (a lot). I'd clean it up myself, but it's taken me most of this afternoon to work this out and the boss will have my hide if he finds out how little I've produced today.
#!/usr/bin/perl -w use strict; use Dumpvalue; # example structure my $struct = { fruit => [qw( apple pear )], type => [qw( organic farmed )], period => { 20050824 => { to => [ 'new york', 'l +ondon', ], transport => 'air' }, 20050825 => { to => 'auckland', }, } , name => 'bangers', }; #logHere $struct; print "Input:\n"; Dumpvalue->new->dumpValue( $struct ) ; my $expanded = expand($struct, []); print '='x80,"\nResult:\n"; Dumpvalue->new->dumpValue( $expanded ) ; sub expand { my ( $struct, $expanded, $parent_key) =@_; for my $key ( sort keys %$struct ) { if ( ref($struct->{$key}) eq 'ARRAY' ) { if ( scalar @$expanded ) { $expanded = [ map { my $row = $_; map { my $newrow = {%$row}; +$newrow->{$key} = $_; $newrow } @{$struct->{$key}} } @$expanded ]; } else { push @$expanded, map { { $key=> $_ } } @{$struct->{$key}} ; } } elsif ( ref $struct->{$key} eq 'HASH' ) { $parent_key ||=''; my $expansion = expand($struct->{$key}, [], $key); $expansion = [map { {%$_, $parent_key => $key} } @$expansion] +if $parent_key; if ( scalar @$expanded ) { my $count = scalar @$expanded; $expanded = [ map { my $row = $_; map { my $newrow = {%$row}; @$newrow{keys %$_} = values %$_ unle +ss $newrow->{$parent_key}; $newrow } @$expansion ; } @$expanded ]; push @$expanded, @$expansion if $count == scalar @$expanded; } else { push @$expanded, @$expansion ; } } else { if ( scalar @$expanded ) { map { $_->{$key} = $struct->{$key} } @$expanded; } else { push @$expanded, { $key => $struct->{$key} }; } } } return $expanded; }

Replies are listed 'Best First'.
Re^2: Expanding / flattening a structure
by jaa (Friar) on Aug 25, 2006 at 15:08 UTC

    Oh Fabulous Bangers! Thank you very much!

    Exactly what I was looking for - and I can now see how you managed to stitch together a mild case of recusion... and the embeded map/map cartesian product - rather nifty indeed!

    I have made one slight performance tweak:

    my $count = scalar @$expanded; $expanded = [ map { my $row = $_; map { my $newrow = {%$row}; @$newrow{keys %$_} = values %$_ unless $newrow->{$parent_key}; $newrow } @$expansion ; } @$expanded ]; push @$expanded, @$expansion if $count == scalar @$expanded;
    to:
    if ( $expanded->[0]{$parent_key} ) { push @$expanded, @$children; } else { # create cartesian product against existing $expanded $expanded = [ map { my $row = $_; map { my $newrow = {%$row}; # clone expanded row @$newrow{keys %$_} = values %$_; # add in parent_key details $newrow # return new row } @$children; } @$expanded ]; }
    as this tests for the presence of $parent_key once, rather than using the before/after count to decide if all the children are new.

    Mucho kudos! and many many thanks

    Jeff