Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

Expanding / flattening a structure

by jaa (Friar)
on Aug 24, 2006 at 18:25 UTC ( [id://569412]=perlquestion: print w/replies, xml ) Need Help??

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

Dear Monks,

I have a compact structure like this:

# example structure my $struct = { fruit => [qw( apple pear )], type => [qw( organic farmed )], period => { 20050824 => { to => 'new york', to => 'london', }, 20050825 => { to => 'auckland', }, } };
I need to expand / flatten it out to this:
# expanded into: my $expanded => [ { fruit => 'apple', type => 'farmed', period => 20050824, to => +'london' }, { fruit => 'apple', type => 'farmed', period => 20050824, to => +'new york' }, { fruit => 'apple', type => 'farmed', period => 20050825, to => +'auckland' }, { fruit => 'apple', type => 'organic', period => 20050824, to => +'london' }, { fruit => 'apple', type => 'organic', period => 20050824, to => +'new york' }, { fruit => 'apple', type => 'organic', period => 20050825, to => +'auckland' }, { fruit => 'pear', type => 'farmed', period => 20050824, to => +'london' }, { fruit => 'pear', type => 'farmed', period => 20050824, to => +'new york' }, { fruit => 'pear', type => 'farmed', period => 20050825, to => +'auckland' }, { fruit => 'pear', type => 'organic', period => 20050824, to => +'london' }, { fruit => 'pear', type => 'organic', period => 20050824, to => +'new york' }, { fruit => 'pear', type => 'organic', period => 20050825, to => +'auckland' }, ];
I know that there is an elegant solution (recursive?) somewhere, but I am a bit stuck on how to approach it.

Any suggestions would be greatly appreciated.

Regards & thanks,

Jeff

Replies are listed 'Best First'.
Re: Expanding / flattening a structure
by ikegami (Patriarch) on Aug 24, 2006 at 18:52 UTC

    Algorithm::Loops's NestedLoops can handle this.

    use strict; use warnings; use Algorithm::Loops qw( NestedLoops ); my %data = ( fruit => [qw( apple pear )], type => [qw( farmed organic )], period => { 20050824 => { to => [ 'new york', 'london' ], }, 20050825 => { to => [ 'auckland' ], }, } ); my $iter = NestedLoops([ $data{fruit}, $data{type}, [ keys %{$data{period}} ], sub { $data{period}{$_[2]}{to} }, ]); my @expanded; while (my @list = $iter->()) { my %row; @row{qw( fruit type period to )} = @list; push @expanded, \%row; } require Data::Dumper; print(Data::Dumper::Dumper(\@expanded));

    Update: Fixed typo in link.

Re: Expanding / flattening a structure
by Fletch (Bishop) on Aug 24, 2006 at 18:29 UTC

    Right off the bat you've got a problem before you even start on your question. Your sub hash period's key 20050824 has duplicate values for the same key (to). That either needs to be an arrayref (to => [ 'new york', 'london' ]), or you need to rethink your data structure some.

Re: Expanding / flattening a structure
by holli (Abbot) on Aug 24, 2006 at 18:29 UTC
    20050824 => { to => 'new york', to => 'london', },
    You cannot have a hash that has the same key twice.


    holli, /regexed monk/
      Apologies, you are right, here is a better example of the input:
      my $struct = { fruit => [qw( apple pear )], type => [qw( farmed organic )], period => { 20050824 => { to => ['new york', 'london', ], }, 20050825 => { to => ['auckland', ], }, } };
      update: fixed first date [ -> {
        Maybe:
        # example structure my $struct = { fruit => [qw( apple pear )], type => [qw( farmed organic )], period => { 20050824 => [ to => ['new york', 'london' ], ], 20050825 => [ to => ['auckland' ], ], }, };
        Would be syntactically valid?

        But given the only attribute to the date is to, why not remove that layer?

        jdtoronto

Re: Expanding / flattening a structure
by planetscape (Chancellor) on Aug 25, 2006 at 01:05 UTC
Re: Expanding / flattening a structure
by bangers (Pilgrim) on Aug 25, 2006 at 14:28 UTC
    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; }

      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

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://569412]
Approved by holli
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (2)
As of 2024-04-26 04:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found