Wise monks,

Recently, I asked a question about functions with analogous properties to perl map and grep, only for hashes. I now realize it is useful to change the spec a bit: a map function that operates on hashes and is composable. This would probably be trivial in a language which supported "functional programming", but my belief is you can probably do this in Perl as well.

The following script uses the version of hmap suggested previously by BrowserUK. I learned a lot by taking this little gem apart (it's humbling to me that some people can do so much in so few lines):

use Data::Dumper; my $h = { 1 => { nick => 'rbush', phone => '5551212', bday => '3/12/1965', }, 3 => { nick => 'ernest', phone => '5553300', bday => '3/12/1971', }, 5 => { nick => 'fred', phone => '1112300', bday => '5/01/1972', }, }; sub hmap (&%) { my $code = shift; my @i = @_; local @_; my @rv; push @rv, $code->(@_=(shift @i,shift @i)) while @i; @rv; } my %new = hmap { $_[1]{nick} =~ /rbush|fred/ ? ($_[0], $_[1]) : () } %$h; my %new2 = hmap { $_[1]{nick} =~ /rbush|fred/ ? ($_[0], hmap { $_[0], $_[1] } %{$_[1]}) : () } %$h; print "new = " . Dumper (\%new) . "\n"; print "new2 = " . Dumper (\%new2) . "\n";

Now we run it:

owncloselady-lm:scratch rbush$ perl hmap_ex1.pl new = $VAR1 = { '1' => { 'nick' => 'rbush', 'bday' => '3/12/1965', 'phone' => '5551212' }, '5' => { 'nick' => 'fred', 'bday' => '5/01/1972', 'phone' => '1112300' } }; new2 = $VAR1 = { 'rbush' => 'bday', '1' => 'nick', '3/12/1965' => 'phone', 'nick' => 'fred', 'phone' => '1112300', 'bday' => '5/01/1972', '5551212' => '5' };

Note that we have a multi-level hash (a hash of hashes). Our first example with "%new" works as expected. We are simply selecting each subhash, based on the nickname. The second test with "%new2" is an attempt to put a "do nothing" hmap "inside the loop" so to speak (composition). This is a simple test to see if composition will work. The expectation is I will get the same result as %new, since I'm just copying inputs to outputs.

However, it doesn't preserve the structure. It ends up removing one level of hash structure, and mixing up the keys. It's possible I'm invoking this wrong, or that this is intentional (several perl programmers mentioned they just throw away the structure and flatten things out).

The next script shows my implementation of hmap which allows composition (and preserves structure). The ultimate task is to select records (i.e. with nick = rbush|fred), and then return only the "nick" and "phone" attributes (the next level of the hashes).

use Data::Dumper; my $h = { 1 => { nick => 'rbush', phone => '5551212', bday => '3/12/1965', }, 3 => { nick => 'ernest', phone => '5553300', bday => '3/12/1971', }, 5 => { nick => 'fred', phone => '1112300', bday => '5/01/1972', }, }; sub hmap { my ($h, $code) = @_; return undef unless defined $h; my $rv = {}; while (my ($k, $v) = each %$h) { my $x = &$code($k, $v); $rv->{$k} = $x if (defined $x); } return $rv; } my $new = hmap $h, sub { $_[1]->{nick} =~ /rbush|fred/ ? $_[1] : undef }; my $new2 = hmap $h, sub { return hmap $_[1]->{nick} =~ /rbush|fred/ ? $_[1] : undef, sub { $_[0] =~ /nick|phone/ ? $_[1] : undef } }; print "new = " . Dumper ($new) . "\n"; print "new2 = " . Dumper ($new2) . "\n";

Now we run this version:

owncloselady-lm:scratch rbush$ perl hmap_ex2.pl new = $VAR1 = { '1' => { 'nick' => 'rbush', 'bday' => '3/12/1965', 'phone' => '5551212' }, '5' => { 'nick' => 'fred', 'bday' => '5/01/1972', 'phone' => '1112300' } }; new2 = $VAR1 = { '1' => { 'nick' => 'rbush', 'phone' => '5551212' }, '5' => { 'nick' => 'fred', 'phone' => '1112300' } };

Note the preserved structure for new2. However, it seems there is some clunkiness. For example, I have to use "sub". Any suggestions on how I can make this better? It's not super important that the implementation of hmap be short and elegant, but that the many _uses_ of hmap be short and elegant. Anything that reduces the number of characters you need to type in an expression, increases robustness or flexibility is helpful.


In reply to hmap revisited by zerohero

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.