laziness, impatience, and hubris PerlMonks

### Using map to create a hash/bag from an array

by spurperl (Priest)
 on Dec 17, 2005 at 10:23 UTC Need Help??
spurperl has asked for the wisdom of the Perl Monks concerning the following question:

Dear monks,

A few minutes ago I needed to "fold" an array into a hash representing a bag/set. That is, from [a, b, c] I wanted to create {a => 1, b => 1, c => 1}. Without thinking much, I slapped:

```my %bag = map {(\$_, 1)} @arr;

And now I wonder on the merits of this technique. I recall seeing it discussed in the monastery before, I can't don't know what to look for in the Supersearch. Any fellow monks with memory better than mine ?

What I want to know, basically, is whether it's a Good Way (TM), what are the alternatives, etc.

Replies are listed 'Best First'.
Re: Using map to create a hash/bag from an array
by GrandFather (Sage) on Dec 17, 2005 at 10:41 UTC

The idiomatic way to do that in Perl is @hash{qw(a b c)} = (1) x 3;:

```use strict;
use warnings;
use Data::Dumper;

my %hash;
@hash{qw(a b c)} = (1) x 3;
print Dumper (\%hash);

Prints:

```\$VAR1 = {
'c' => 1,
'a' => 1,
'b' => 1
};

DWIM is Perl's answer to Gödel
Except, IMHO, the hash values are irrelevant. Codewise, it's easier to use undef as hash values: your
```@hash{qw(a b c)} = (1) x 3;
then becomes
```@hash{qw(a b c)} = ();

That's a lot shorter, isn't it? And no need to count items.

And faster too (making the obvious benchmark addition):

```         Rate   set   map map++    GF  Bart
set    2288/s    --  -84%  -91%  -92%  -93%
map   14566/s  537%    --  -46%  -48%  -58%
map++ 26756/s 1070%   84%    --   -4%  -23%
GF    27881/s 1119%   91%    4%    --  -19%
Bart  34580/s 1412%  137%   29%   24%    --

See Re: Using map to create a hash/bag from an array for the original code

DWIM is Perl's answer to Gödel
Re: Using map to create a hash/bag from an array
by GrandFather (Sage) on Dec 17, 2005 at 11:21 UTC

Benchmarks are interesting:

```use strict;
use warnings;
use Set::Scalar;
use Benchmark qw(cmpthese);

my @array = qw(a b c d e f g h i j k l m n o p q r s t u v w x y z);

cmpthese
(-1,
{
'GF' => sub {my %hash; @hash{@array} = (1) x @array;},
'map' => sub {my %hash = map {(\$_, 1)} @array;},
'map++' => sub {my %hash; map {\$hash{\$_}++} @array;},
'set' => sub {my \$set = Set::Scalar->new(@array);},
}
);

Prints:

```         Rate   set   map map++    GF
set    2203/s    --  -84%  -91%  -92%
map   13740/s  524%    --  -46%  -49%
map++ 25598/s 1062%   86%    --   -6%
GF    27113/s 1131%   97%    6%    --

DWIM is Perl's answer to Gödel
Didn't they optimize map at some point so it doesn't actually creat a new array when in a null context? That would explain why map++ is so much faster than map.

-Lee

perl digital dash (in progress)
Re: Using map to create a hash/bag from an array
by jdporter (Canon) on Dec 17, 2005 at 14:19 UTC

Semantic quibble: what you are talking about is not a bag, it is a set. A bag is just like a set, except that elements can occur more than once. You can use a hash to represent a bag, but it's a bit more involved. One simple implementation could have a key's value indicate the number of times the key occurs in the bag; but you'd still have to override the hash delete function so that it decrements the count, and only deletes the key when the count goes to zero. Another, more obvious, implementation would be to use an array. Then you'd have to write exists and delete functions. Hiding all this behind TIE would probably make some sense. All that being said, you could just use Set-Bag.

We're building the house of the future together.
Re: Using map to create a hash/bag from an array
by osunderdog (Deacon) on Dec 17, 2005 at 10:50 UTC

Well, it seems to work... So that's certainly a good start. I use two alternatives for this. It depends on whether I need to know the frequency of items or if I need more set like semantics.

Here is the bag example:

```use strict;
use Data::Dumper;

my @items = ('this', 'that', 'that', 'those', 'these', 'them', 'thou')
+;

my \$bag;
map {\$bag->{\$_}++} @items;

print Dumper(\$bag);

__END__

\$VAR1 = {
'these' => 1,
'those' => 1,
'thou' => 1,
'them' => 1,
'that' => 2,
'this' => 1
};

And here is the set example:

```use strict;
use Set::Scalar;
use Data::Dumper;

my @items = ('this', 'that', 'that', 'those', 'these', 'them', 'thou')
+;
my \$set = Set::Scalar->new(@items);

print Dumper(\$set->members);

__END__

\$VAR1 = 'those';
\$VAR2 = 'these';
\$VAR3 = 'thou';
\$VAR4 = 'them';
\$VAR5 = 'that';
\$VAR6 = 'this';

Hazah! I'm Employed!

Why not use for in this example, since you're not really transforming @items into a new array (common use of map), but rather just performing an action for each value in @items (common use of for).

```\$bag->{\$_}++ for @items;
map {\$bag->{\$_}++} @items;

-Bryan

Create A New User
Node Status?
node history
Node Type: perlquestion [id://517458]
Approved by GrandFather
help
Chatterbox?
 [moritz]: [ { a => [ { b => 1 } ] } ] [choroba]: dereference ne creation [choroba]: at least in my lingo [moritz]: .oO( oh temps, oh moritz ) [GotToBTru]: you are correct, choroba, but of course there is no point in doing one without the other [GotToBTru]: un less you're working on write-only memory [1nickt]: my \$x = \$aohoaoh->[0]->{' foo'}->[0]->{'bar' }; should work [thepkd]: not indexing [thepkd]: getting data out of the ds [choroba]: it depends. Give more details, please

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (14)
As of 2016-12-06 13:21 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
On a regular basis, I'm most likely to spy upon:

Results (104 votes). Check out past polls.