Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Hash assignments using map

by njcodewarrior (Pilgrim)
on Feb 24, 2007 at 15:47 UTC ( [id://601881]=perlquestion: print w/replies, xml ) Need Help??

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

Why does:

my @keys = qw{ a b c d }; my %hash = map { $_++ } @keys;

Produce the following warning:

Odd number of elements in hash assignemnt.

while the following does not:

my %hash; foreach ( @keys ) { $hash{$_}++; }

I thought the map method was just a short hand way of using a loop?

Thanks in advance - njcodewarrior

Replies are listed 'Best First'.
Re: Hash assignments using map
by chargrill (Parson) on Feb 24, 2007 at 16:35 UTC

    There's no reason for that warning. You need to double check the code you're copying:

    $ perl -Mstrict -we 'my @a = qw(a b c d); my %h = map { $_++ } @a' $ perl -Mstrict -we 'my @a = qw(a b c d e);my %h = map { $_++ } @a' Odd number of elements in hash assignment at -e line 1. $


    --chargrill
    s**lil*; $*=join'',sort split q**; s;.*;grr; &&s+(.(.)).+$2$1+; $; = qq-$_-;s,.*,ahc,;$,.=chop for split q,,,reverse;print for($,,$;,$*,$/)
Re: Hash assignments using map
by Herkum (Parson) on Feb 24, 2007 at 16:33 UTC
    I ran the code on my box, Windows XP with Activestate Perl 5.8.8 and I did not get that error.
    #!/usr/bin/perl -w use strict; use Data::Dump qw(dump); my @keys = qw{ a b c d }; my %hash = map { $_++ } @keys; warn "Dump " . dump( %hash ) . "\n";
    #!/usr/bin/perl -w use strict; use Data::Dump qw(dump); my @keys = qw{ a b c d }; my %hash = (); for (@keys) { $hash{$_}++; } warn "Dump " . dump( %hash ) . "\n";
    what I did get was,
    Dump ("c", "d", "a", "b") # first script Dump ("a", 1, "c", 1, "b", 1, "d", 1) # second script

    Neither answer seems to be exactly what you would I want, I think.

    How about telling us what you are trying to do and see if we can address that issue?

      I've got a HoH data structure for which I'm looking to keep only certain entries. The entries I'm looking for are contained in a list and I'd like to eliminate the entries which are not contained in that list ( I've substituted a straight hash in place of my HoH for simplicity's sake):

      #! /usr/bin/perl use strict; use warnings; use Data::Dumper; # Data Structure my %h = ( 'a' => 'z', 'b' => 'y', 'c' => 'x', 'd' => 'w', ); my @to_keep = qw{ a c }; my %keepers = map { $_ => 1 } @to_keep; foreach ( keys %h ) { if ( !exists $keepers{$_} ) { delete $h{$_}; } } $Data::Dumper::Varname = 'h'; print Dumper( \%h ); $h1 = { 'c' => 'x', 'a' => 'z' };

      I'm creating the %keepers hash out of the items in @to_keep to compare against all of the keys in %h. Since I can create the hash this way:

      my @to_keep = qw{ a c }; my %keepers; foreach ( @to_keep ) { $keepers{$_}++; } $Data::Dumper::Varname = 'keepers'; print Dumper( \%keepers ); keepers1 = { 'c' => 1, 'a' => 1 };

      I thought I could do the same thing using map:

      my @to_keep = qw{ a c }; my %keepers; %keepers = map { $_++ } @to_keep; $Data::Dumper::Varname = 'keepers'; print Dumper( \%keepers ); $keepers1 = { 'a' => 'c' };

      But it doesn't seem to work.

      njcodewarrior

        my @to_keep = qw{ a c }; my %keepers; foreach ( @to_keep ) { $keepers{$_}++; }
        ... I thought I could do the same thing using map ...

        The way to do that with map would be like this:

        my %keepers = map { $_ => 1 } @to_keep;

        The point is that, in this case, you want each iteration in map to return a key/value pair, not just a single value, and the "fat comma" (=>) does that for you.

Re: Hash assignments using map
by friedo (Prior) on Feb 24, 2007 at 16:06 UTC
    Your map statement is saying "take the elements of @keys and return a list consisting of each element, incremented." When you assign a list to a hash, Perl expects an even-numbered list of key-value pairs.

      Hi friedo

      I'm still not clear on this. Isn't that what I'm doing in here:

      my %hash; my @keys = qw{ a b c d }; foreach ( @keys ) { $hash{$_}++; }

      Can you clarify for me?

      thanks - njcodewarrior

        njcodewarrior,
        You're right in that the map function acts like a loop. Where is gets tricky is when you assign the output of the map function to a hash, because the hash assignment operation evaluates two elements at a time. So Perl does the equivalent of this:
        my $i=0; while ($i<$#group) { $hash{$group[$i]}=$group[$i+1]; $i+=2; }
        As other monkers already pointed out the "odd number of elements" warning will be generated when you feed the hash with a group that has an odd number of elements.

        Now if we look at the map function itself in your code

        @destination = map {$_++} @source
        The effect of the above code is that the elements are copied one by one. At the same time the element in the source (!) group is changed/incremented. Thatīs probably not what you wanted, right?

        If the destination of the map function is a hash then commonly the map is used to produce 2 elements at a time in a list fashion:

        .... = map { ("key$_" , "value$_") } .....
        For clarity purpose the comma operator is typically replaced by a '=>' operator to indicate that we mean to produce something for a hash.
        .... = map { ("key$_" => "value$_") } .....
        I'm not sure what you hoped to achieve through your code but hopefully this shows why your code behaved as it did
        the first statement takes the elements from the array @keys, increments them and adds to the hash %hash.
        first point is, you only have letters, no numbers, so Perl won't "increment" them, and will just add them as they already are to the hash.
        secont point, the hash takes two elements to populate a key-value pair. for example, a hash %h = (a => 1, b => 2, c => 3) could also be written as %h = qw{a 1 b 2 c 3}.
        the second statement takes each element from the array as a key of the hash, incrementing its value each time it's processed (which means if you have twice the element "a" in the array, the value of the key "a" in the hash will be 2 etc). if you want to use map to do that, it'd like map { $hash{$_}++ } @keys.

        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        *women.pm
Re: Hash assignments using map
by johngg (Canon) on Feb 24, 2007 at 18:16 UTC
    If you are trying to initialise a hash with four keys ("a" to "d") each with a value of 1 using map you need to pass your four keys into the map but pass eight things out the other side, 'a', 1, 'b', 1, 'c', 1, 'd', 1. This is because a hash with four key/value pairs will flatten to a list of eight elements. You can do it like this.

    my @keys = qw{a b c d}; my %hash = map { $_ => 1 } @keys;

    Your loop alternative could be written

    my @keys = qw{a b c d}; my %hash; $hash{$_} ++ for @keys;

    Another alternative is to use a hash slice

    my @keys = qw{a b c d}; my %hash; @hash{@keys} = (1) x scalar @keys;

    I hope this is of use.

    Cheers,

    JohnGG

Re: Hash assignments using map
by japhy (Canon) on Feb 25, 2007 at 00:15 UTC
    In addition to what others have said, you could do:
    my @to_keep = (...); my %keepers; @keepers{@to_keep} = ();
    which you would then test with exists $keepers{$x}. Althought I'm curious why you need the %keepers hash if you have the @to_keep array.

    Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
    How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart

      Hi japhy

      Not to beat a dead horse here, but to answer your question:

      I'm checking the keys in another hash against those in %keepers. If a keys exists in this other hash, but NOT in %keepers, I want to eliminate it from the hash. See this node for further explanation.

      Anyways, thanks for the clarification

      njcodewarrior

        Right. My point is, why not loop over the elements in @keepers, rather than the keys of the other hash?
        my %big_hash = (...); my @to_keep = ('abc', 'xyz'); %big_hash = map { exists($big_hash{$_}) ? ($_ => $big_hash{$_}) : () } + @to_keep; # or %big_hash = map { $_ => $big_hash{$_} } grep { exists $big_hash{$_} } +@to_keep;

        Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
        How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart
Re: Hash assignments using map
by NetWallah (Canon) on Feb 24, 2007 at 16:38 UTC
    What platform and perl version are you on ?

    The code runs without warnings, producing hash keys of "c" and "a", as expected, for me:

    Summary of my perl5 (revision 5 version 8 subversion 8) configuration: Platform: osname=MSWin32, osvers=5.0, archname=MSWin32-x86-multi-thread

         "A closed mouth gathers no feet." --Unknown

Re: Hash assignments using map
by Not_a_Number (Prior) on Feb 25, 2007 at 10:32 UTC

    Here's another way to do what you want, without creating an intermediate %keepers hash:

    use strict; use warnings; use Data::Dumper; # Data Structure my %h = ( 'a' => 'z', 'b' => 'y', 'c' => 'x', 'd' => 'w', ); my @to_keep = qw{ a c }; %h = map { $_ => $h{$_} } @to_keep; print Dumper \%h;

    Update: If by chance your @to_keep array might contain elements that do not necessarily occur as keys in your input hash, it would probably be better to change the penultimate line to:

    %h = map { $_ => $h{$_} } grep { defined $h{$_} } @to_keep;

      Hello there Not a Number

      That's very nice and simple!

      Thanks very much for your help! - njcodewarrior

      %h = map { $_ => $h{$_} } @to_keep;
      is the same as
      %h = @h{@to_keep};

      Update: Oops, no! It would actually be equivalent to the more complicated:

      my %temp; @temp{@to_keep} = @h{@to_keep}; %h = %temp;

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others taking refuge in the Monastery: (5)
As of 2024-04-24 09:21 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found