Beefy Boxes and Bandwidth Generously Provided by pair Networks vroom
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Specific hash to array conversion query

by monarch (Priest)
on Jul 12, 2005 at 02:17 UTC ( #474152=perlquestion: print w/ replies, xml ) Need Help??
monarch has asked for the wisdom of the Perl Monks concerning the following question:

Hi esteemed PerlMonks!

I've enjoyed assisting, where I can, others with very similar queries to the one I'm asking here, but I'm having a mental block and can't seem to think of a way to do the following.

I have a hash:

%properties = ( if_mac => { 0 => { value => '00:11:1A:F2:E1:92', fixed => 0 }, 1 => { value => '00:11:1A:F2:E1:93', fixed => 0 }, }, if_ip => { 0 => { value => '132.181.30.3', fixed => 0 }, 1 => { value => '132.181.30.4', fixed => 0 }, } );

I want to convert it into a flat array of just the properties:

my @values = ( [ 'if_mac', 0, '00:11:1A:F2:E1:92' ], [ 'if_mac', 1, '00:11:1A:F2:E1:93' ], [ 'if_ip', 0, '132.181.30.3' ], [ 'if_ip', 1, '132.181.30.4' ] );

I could loop over the hash and build up the array, but I would like to be more clever and use the map { } function. The problem is that I am trying to build an array of arrays with three columns in it. I solved a similar problem in another perlmonk question (Re: Hash of array of hashes in reply to Hash of array of hashes) by creating an anonymous array reference in a sub-map function call.. I wonder if something similar could be done here?

Any ideas?

Update: changed curly braces to parenthesis on suggestion by davidrw. Problem solved neatly by Transient.

Comment on Specific hash to array conversion query
Select or Download Code
Re: Specific hash to array conversion query
by davidrw (Prior) on Jul 12, 2005 at 02:45 UTC
    Here's a map solution (note that you can't depend on the order that keys provides, so if order matters you'll have to put a sort or two in there; I also assumed that there was just 0 and 1 keys in there):
    my @values = map { ( [ $_, 0, $properties{$_}->{0}->{value} ], [ $_, 1, $properties{$ +_}->{1}->{value} ] ) } keys %properties;
    Also note that your %properties = { ... } needs to be %properties = ( ... )
      Nice answer, thankyou!

      What if there were other keys besides 0 and 1.. that appears to be the hardest part of all..

        thanks! For other keys, you can nest a map in there .. trick is to use a temp variable for the outer $_. Remember that you can put any code inside the map--it's only the value of the last statement that matters/is returend. I also tossed a sort of the file results in there so that it's exactly what you put in your OP:
        my @values = sort { $b->[0] cmp $a->[0] || $a->[1] <=> $b->[1] } map { my $k = $_; map { [ $k, $_, $properties{$k}->{$_}->{value} ] } keys %{$propert +ies{$k}}; } keys %properties;
        Update: Now that i read the rest of the thread and see that Zaxo and Transient provided the exact same nested map.. I like the post-sort on mine though :)
Re: Specific hash to array conversion query
by Transient (Hermit) on Jul 12, 2005 at 02:53 UTC
    Here's one with a subselect on the keys (you can do a sort to get whatever order for the keys - left as an exercise for the reader ;)
    #!/usr/bin/perl use warnings; use strict; use Data::Dumper; my %properties = ( if_mac => { 0 => { value => '00:11:1A:F2:E1:92', fixed => 0 }, 1 => { value => '00:11:1A:F2:E1:93', fixed => 0 }, }, if_ip => { 0 => { value => '132.181.30.3', fixed => 0 }, 1 => { value => '132.181.30.4', fixed => 0 }, } ); my @values = map { my $key=$_; map { [ $key, $_, $properties{$key}->{$_}->{value} + ] } keys %{$properties{$_}}} keys %properties; print Dumper(\%properties); print "\n"; print Dumper(\@values), "\n"; __OUTPUT__ $VAR1 = { 'if_ip' => { '1' => { 'fixed' => 0, 'value' => '132.181.30.4' }, '0' => { 'fixed' => 0, 'value' => '132.181.30.3' } }, 'if_mac' => { '1' => { 'fixed' => 0, 'value' => '00:11:1A:F2:E1:93' }, '0' => { 'fixed' => 0, 'value' => '00:11:1A:F2:E1:92' } } }; $VAR1 = [ [ 'if_ip', '1', '132.181.30.4' ], [ 'if_ip', '0', '132.181.30.3' ], [ 'if_mac', '1', '00:11:1A:F2:E1:93' ], [ 'if_mac', '0', '00:11:1A:F2:E1:92' ] ];
      That is perfect. I like the way you've saved the value of the outer key map { my $key=$_; map... and used that in the inner query.
Re: Specific hash to array conversion query
by Zaxo (Archbishop) on Jul 12, 2005 at 03:06 UTC

    Interesting challenge to build that with map. The difficulty is that the expansions will need to be somewhat inside-out. Lets try building it one step at a time, outside-in.

    Given %properties, we need to work first with its keys, since their names are part of the output. So, my @values = map { foo( $_) } keys %properties;
    where foo() takes a key of %properties and returns a list of array references taken from that key.

    Making an actual sub foo() for that is probably not justified, so we'll expand foo's definition in place,

    my @values = map { my $key = $_; map { bar( $key, $_) } keys %{$properties{$_}} } keys %properties;
    bar() is just like foo(), but it has more arguments to help build our array references.

    In fact, bar() has all the arguments we need, so we expand it again,

    my @values = map { my $key = $_; map { [ $key, $_, $properties{$key}{$_}{'value'} ] } keys %{$properties{$_}} } keys %properties;
    There it is, with map, all in one statement.

    That doesn't bother to sort the keys, but if you want that, you can insert it,

    my @values = map { my $key = $_; map { [$key, $_, $properties{$key}{$_}{'value'}] } sort {$a <=> $b} keys %{$properties{$_}} } sort keys %properties;

    It's a good idea to avoid one-lining constructions like this. It makes them very hard to read if you skip indentation.

    After Compline,
    Zaxo

Re: Specific hash to array conversion query
by revdiablo (Prior) on Jul 12, 2005 at 16:44 UTC
    I could loop over the hash and build up the array, but I would like to be more clever and use the map { } function.

    I'd like to step back for a moment. This node is interesting, and the map solutions are very neat and clever. I know we all like writing this kind of code. It's certainly exciting when you can look at a block of code like this and understand what it's doing.

    I have to wonder if it's the best approach, though. An iterative solution would be easier to write, and easier to read later, saving time on both ends. I guess it's a moot point now that the clever solutions have been provided, but it might be worth thinking about.

    Incidentally, if the thing you don't like about the iterative solution is that you have to use push instead of having everything as an rvalue (which I admit, does feel kind of clumsy), then I have good news for you. This is yet another thing Fixed in 6:

    %properties = ( if_mac => { 0 => { value => '00:11:1A:F2:E1:92', fixed => 0 }, 1 => { value => '00:11:1A:F2:E1:93', fixed => 0 }, }, if_ip => { 0 => { value => '132.181.30.3', fixed => 0 }, 1 => { value => '132.181.30.4', fixed => 0 }, } ); my @properties = gather { for %properties.keys -> $type { for %properties{$type}.keys -> $num { take [ $type, $num, %properties{$type}{$num}{'value'} ]; } } };

    The gather/take construct allows you to use an iterative algorithm as an rvalue. I think I will end up using it a lot. :-)

    Update: some may point out that map really is iterative, just in a sort of interesting way. I guess that's true. So perhaps s/iterative/for loop/g on my node.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://474152]
Approved by GrandFather
Front-paged by planetscape
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (7)
As of 2014-04-19 20:58 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (483 votes), past polls