Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Generating Hash-of-List using map?

by cmv (Chaplain)
on Sep 23, 2013 at 16:19 UTC ( #1055327=perlquestion: print w/ replies, xml ) Need Help??
cmv has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks-

I don't quite understand why this one is so hard for me to figure out - please help!

This code:

use warnings; use strict; use Data::Dumper; my @CELLS=( 'A-1', 'A-2', 'A-3', 'A-4', 'B-5', 'B-6', 'C-7', 'C-8'); my %hash=map { /^(.*)-(\d+)$/; $1=>$2} @CELLS; print STDERR "hash DUMPER:\n", Dumper(\%hash), "\n";
Generates a Hash whose key is the alphabetical letter found before the dash, and whose value is the last numerical digit found for that letter
hash DUMPER: $VAR1 = { 'A' => '4', 'C' => '8', 'B' => '6' };
How can I change the map command so that it will generate a list of all the numerical digits found? Something like this:
hash DUMPER: $VAR1 = { 'A' => [ 1, 2, 3, 4 ], 'C' => [ 7, 8 ], 'B' => [ 5, 6 ] };

Thanks

-Craig

Comment on Generating Hash-of-List using map?
Select or Download Code
Replies are listed 'Best First'.
Re: Generating Hash-of-List using map?
by moritz (Cardinal) on Sep 23, 2013 at 16:29 UTC

    There are two approaches: either you can add state information to the block of the map, in which case writing it as an ordinary for-loop is probably much less confusing. Or you can do a separate reduction step which keeps the state for you.

    Perl 5 is well suited for the first approach, so here it is:

    use warnings; use strict; use Data::Dumper; my @CELLS=( 'A-1', 'A-2', 'A-3', 'A-4', 'B-5', 'B-6', 'C-7', 'C-8'); my %hash; for (@CELLS) { /^(.*)-(\d+)$/ and push @{ $hash{$1} }, $2 } print STDERR "hash DUMPER:\n", Dumper(\%hash), "\n";

    The second approach becomes much easier when you have objects for pairs, so it's easier to pass them around as a scalar. Here is a Perl 6 implementation:

    use v6; my @CELLS= 'A-1', 'A-2', 'A-3', 'A-4', 'B-5', 'B-6', 'C-7', 'C-8'; my %hash; %hash.push: @CELLS.map: *.split('-'); say %hash.perl;

    If you think that's cheating, because Hash.push does all the heavy work, here is a version which doesn't use it:

    use v6; my @CELLS= 'A-1', 'A-2', 'A-3', 'A-4', 'B-5', 'B-6', 'C-7', 'C-8'; my %hash = @CELLS.map({ my ($k, $v) = .split('-'); $k => $v})\ .categorize(*.key)\ # group by key .map({; .key => [.value>>.value]}) # list of pairs t +o list of values ; say %hash.perl;

    (Updated to add the Perl 6 stuff).

Re: Generating Hash-of-List using map?
by BrowserUk (Pope) on Sep 23, 2013 at 17:14 UTC

    One of the cases where for is just easier than map:

    my %hash; m[(.+)-(\d+)] and push @{ $hash{$1} }, $2 for @CELLS;; pp \%hash;; { A => [1, 2, 3, 4], B => [5, 6], C => [7, 8] }

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Generating Hash-of-List using map?
by LanX (Canon) on Sep 23, 2013 at 17:06 UTC
    probably not an expected answer:

    DB<117> %hash=() DB<118> map { ($k,$e)=split '-'; push @{ $hash{$k} },$e } @CELLS => (1, 2, 3, 4, 1, 2, 1, 2) DB<119> \%hash => { A => [1, 2, 3, 4], B => [5, 6], C => [7, 8] }

    update

    or

    DB<125> %hash=() DB<126> push @{ $hash{$_->[0]} } , $_->[1] for map {[split '-' ]} @ +CELLS => "" DB<127> \%hash => { A => [1, 2, 3, 4], B => [5, 6], C => [7, 8] }

    or

    DB<128> %hash=() DB<129> map { push @{ $hash{$_->[0]} } , $_->[1] } map {[split '-' +]} @CELLS => (1, 2, 3, 4, 1, 2, 1, 2) DB<130> \%hash => { A => [1, 2, 3, 4], B => [5, 6], C => [7, 8] }

    or ( but not recommended b/c of possible side effects)

    DB<135> %hash=() DB<136> map { @_=split '-'; push @{$hash{$_[0]}},$_[1] } @CELLS => (1, 2, 3, 4, 1, 2, 1, 2) DB<137> \%hash => { A => [1, 2, 3, 4], B => [5, 6], C => [7, 8] }

    Cheers Rolf

    ( addicted to the Perl Programming Language)

Re: Generating Hash-of-List using map?
by hdb (Prior) on Sep 23, 2013 at 19:24 UTC

    All proposals are cheating so far! Here is what you are looking for (warning: dirty code!)

    my %hash = map { /^([^-]*)-(.*)$/; if( $1 eq ($a//'-') ) { push @$b, $ +2; () } else { ( $a = $1 => $b = [ $2 ] ) } } @CELLS;

    Of course, should $a accidentally be identical to the bit left of the hyphen in the first element of @CELLS, then it fails...

    UPDATE: Small improvement on the else branch.

      I have the suspicion that this only works with sorted input while the "cheating propsosals" always work...

      DB<148> %hash=() DB<149> @CELLS => ("A-1", "A-2", "A-3", "A-4", "B-5", "B-6", "C-7", "C-8", "A-9") DB<150> %hash = map { /^([^-]*)-(.*)$/; if( $1 eq ($a//'-') ) { push + @$b, $2; () } else { ( $a = $1 => $b = [ $2 ] ) } } @CELLS; => ("B", [5, 6], "C", [7, 8], "A", [9])

      oops, indeed! ;-)

      Cheers Rolf

      ( addicted to the Perl Programming Language)

        Oh, #@%$"!

        I admit defeat. A sort is probably not what the OP specified. One could build a full temporary hash in the map but that would be cheating as well (also relies on $a not been used for anything else before):

        my %hash = map { /^([^-]*)-(.*)$/; exists $$a{$1}? (push(@{$$a{$1}},$2 +) and ()): ( $1=>$$a{$1}=[$2] ) } @CELLS;

        So there is really no way around first declaring the hash and then filling it. What a pity!

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others browsing the Monastery: (10)
As of 2015-07-29 21:09 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The top three priorities of my open tasks are (in descending order of likelihood to be worked on) ...









    Results (269 votes), past polls