Beefy Boxes and Bandwidth Generously Provided by pair Networks
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 ( [id://517458]=perlquestion: print w/replies, xml ) 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 (Saint) 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 (Saint) 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 (Chancellor) 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; # instead of map {$bag->{$_}++} @items;

          -Bryan

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (3)
As of 2024-03-19 04:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found