Re: Hash assignments using map
by chargrill (Parson) on Feb 24, 2007 at 16:35 UTC
|
$ 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($,,$;,$*,$/)
| [reply] [d/l] [select] |
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?
| [reply] [d/l] [select] |
|
#! /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
| [reply] [d/l] [select] |
|
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. | [reply] [d/l] [select] |
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. | [reply] [d/l] [select] |
|
my %hash;
my @keys = qw{ a b c d };
foreach ( @keys ) {
$hash{$_}++;
}
Can you clarify for me?
thanks - njcodewarrior | [reply] [d/l] |
|
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 | [reply] [d/l] [select] |
|
|
|
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
| [reply] [d/l] [select] |
|
|
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 | [reply] [d/l] [select] |
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.
| [reply] [d/l] [select] |
|
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
| [reply] [d/l] [select] |
|
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;
| [reply] [d/l] [select] |
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
| [reply] |
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;
| [reply] [d/l] [select] |
|
| [reply] |
|
%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;
| [reply] [d/l] [select] |