http://www.perlmonks.org?node_id=442097

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

While working on a program, I was using some hashes in various places. Declaring it in one place with default data and appending to it at a later point.

My question is this: There is a "." operator for concatenation strings (scalars), correct? Is there any equivalent to this for hashes?

i.e.
my %hash = ( a => 'b' ); # do stuff %hash .= ( e => 'f', g => 'h', i => 'j' );
instead of the semi-tedious
$hash{'e'} = 'f'; $hash{'g'} = 'h'; $hash{'i'} = 'j';
or the seemingly ineffecient (?)
my @vals = qw( f h j ); my $i = 0; $hash{$_} = $vals[$i++] foreach qw( e g i );
also, how about combining hashes? If not, what method is best for this? Would it be prudent to overload operators to include this kind of functionality (., .=) for hashes?

This is mostly just to satisfy curiosity and, of course, to learn as much as possible :)

Thanks!
--------------
"But what of all those sweet words you spoke in private?"
"Oh that's just what we call pillow talk, baby, that's all."

Replies are listed 'Best First'.
Re: Concerning hash operations (appending, concatenating)
by Zaxo (Archbishop) on Mar 24, 2005 at 16:31 UTC

    This is one idiom,

    %hash = (%hash, e => 'f', g => 'h', i => 'j'); %hash = (%otherhash, %hash);
    Order matters; the last instance of a key/value pair is the one that sticks to the key.

    After Compline,
    Zaxo

      This is actually really, really useful. People often ask the question "how do I store a value in a hash only if it's a new value?". Here's the answer.

      thor

      Feel the white light, the light within
      Be your own disciple, fan the sparks of will
      For all of us waiting, your kingdom will come

        Yeah, but I don't like it. I don't like assigning X to itself like the example does. I'd prefer a slightly long-winded approach:
        $hash{$_} = $otherhash{$_} for grep !exists $hash{$_}, keys %otherhash +;
        Or:
        { my @new_keys = grep !exists $hash{$_}, keys %otherhash; @hash{@new_keys} = @otherhash{@new_keys}; }
        _____________________________________________________
        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
      Thanks for that one Zaxo (among others), I hadn't thought of these ; but is there any way to eliminate the redundancy of flattening the hash in
      %hash = (%hash, e => 'f', g => 'h', i => 'j');
      ??
      --------------
      "But what of all those sweet words you spoke in private?"
      "Oh that's just what we call pillow talk, baby, that's all."

      <This is one idiom,

      <%hash = (%hash, e => 'f', g => 'h', i => 'j'); <%hash = (%otherhash, %hash);

      This code reminds me of writing the comma operator in this way: ",=", so to concatenate a hash:

      my %hash1 =qw/ One 1 Two 2 Three 3/; my %hash2 =qw/ Four 4 Five 5 Six 6/; %hash1 ,= %hash2; say keys %hash1;

      And I've checked it, it works in Perl6, the future version of Perl.

Re: Concerning hash operations (appending, concatenating)
by friedo (Prior) on Mar 24, 2005 at 16:26 UTC
    For this type of thing I generally use slices.

    @hash{qw( f h j )} = @vals[0..2];
Re: Concerning hash operations (appending, concatenating)
by artist (Parson) on Mar 24, 2005 at 16:31 UTC
    my %hash = ( a => 'b' ); # do stuff %hash = (%hash, e => 'f', g => 'h', i => 'j' );
Re: Concerning hash operations (appending, concatenating)
by japhy (Canon) on Mar 24, 2005 at 16:53 UTC
    The problem is that Perl doesn't abstract the concept of appending for its various containers (scalars, arrays, and hashes), because Perl isn't inherently OO. I don't know if Perl 6 is going this route, where you could say:
    $foo.append($bar); @foo.append(@bar); %foo.append(%bar);
    And in the same vein, perhaps:
    $foo.prepend($bar); @foo.prepend(@bar); %foo.prepend(%bar);
    In recent Perls, we can write this as:
    # append($x, $y) appends $y to $x # etc. # prepend() left as a simple exercise to the reader sub append (\[$@%]\[$@%]) { use Scalar::Util 'reftype'; use Carp qw( croak ); my ($l, $lt, $r, $rt) = map { $_, reftype($_) } @_; if ($lt eq $rt) { if ($lt eq 'SCALAR') { $$l .= $$r; } elsif ($lt eq 'ARRAY') { push @$l, @$r; } elsif ($lt eq 'HASH') { @$l{keys %$r} = values %$r; } else { croak "append($lt,$rt) not implemented"; } } else { croak "append($lt,$rt) not implemented"; } }
    I'd expect friction coming from the "what does append/prepend mean for hashes?" faction, but I'd say it's a matter of precedence. Appending to a hash, when there are duplicate keys, uses the new values, and prepending would use the old values.
    _____________________________________________________
    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: Concerning hash operations (appending, concatenating)
by inq123 (Sexton) on Mar 24, 2005 at 18:20 UTC
    Note that %combined = (%hash1, %hash2); suffers from performance penalty. If performance's a concern, it's always better to do map { $hash1{$_} = $hash2{$_} } keys %hash2; (unless you want to keep %hash1 and %hash2 intact, which doesn't seem to be the case in your question)

    use Benchmark; my $size = 100000; my %hash1 = map { $_ => 1 } (0..$size); my %hash2 = map { $_ => 1 } ($size..(2*$size)); my $t0 = new Benchmark; my %combined = (%hash1, %hash2); my $t1 = new Benchmark; print "Combining took:",timestr(timediff($t1, $t0)),"\n"; $t0 = new Benchmark; map { $hash1{$_} = $hash2{$_} } keys %hash2; my $t1 = new Benchmark; print "Map took:",timestr(timediff($t1, $t0)),"\n";
    When size = 100000 and 1000000 respectively, the results:
    inq123@perlmonks$ perl test.pl
    Combining took: 0 wallclock secs ( 0.28 usr +  0.02 sys =  0.30 CPU)
    Map took: 0 wallclock secs ( 0.18 usr +  0.00 sys =  0.18 CPU)
    inq123@perlmonks$ perl test.pl
    Combining took:42 wallclock secs (41.53 usr +  0.21 sys = 41.74 CPU)
    Map took: 2 wallclock secs ( 1.89 usr +  0.05 sys =  1.94 CPU)
    
    So performance penalty is manifested when the hashs contain tens of thousands of elements, which is not too rare.
      General advice. Just because there are many ways to do it in Perl is not a reason for picking a less readable one. If you are not returning data from map, then you should write it like this:
      $hash1{$_} = $hash2{$_} for keys %hash2;
      That is easier to read, and much more clearly signals intent. It is also at least as fast as the map version. (It used to be a lot faster, but in Perl 5.8 there is an optimization that causes map to shortcircuit to become a for if it is in null context.)

      Furthermore performance is far less likely to matter than most people think, and when it does having micro-optimized as you went is generally a bad strategy for getting it. (You want to keep code clean and then look for a better algorithm, or move a small section into C.) Therefore I would generally use the following strategy because it is even clearer, even though it is marginally slower on my machine (about 10% so):

      @hash1{keys %hash2} = values %hash2;
      And, of course, in the rare case that performance really mattered and I really wanted to work in Perl, it is fastest to avoid having to do 2 sets of hash lookups on %hash2:
      $combined{$k} = $v while my ($k, $v) = each %hash2;
Re: Concerning hash operations (appending, concatenating)
by sh1tn (Priest) on Mar 24, 2005 at 16:35 UTC
    #basically the same: %h_1 = (a,1,b,2); %h_2 = (c,3,d,4); %concat = (%h_1,%h_2); print "k: $_\tv: $concat{$_}\n" for sort keys %concat; __END__ k: a v: 1 k: b v: 2 k: c v: 3 k: d v: 4


Re: Concerning hash operations (appending, concatenating)
by manav (Scribe) on Mar 24, 2005 at 16:53 UTC
    The above solutions rely on interpreting the hash in a list context, in which it returns all its key value pairs flattened out. for eg, see the below code
    use strict ; use warnings ; my %hash=('a'=>'b','c'=>'d') ; my @arr=(%hash) ; local $"="|" ; print "@arr" ;
    which spews out
    a|b|c|d
    Now,
    (%hash,'e'=>'f')
    flattens out the key value pairs of %hash into a list. Hence it reduces to
    ('a'=>'b','c'=>'d','e'=>'f')
    So on and so forth for code like
    (%hash1,%hash2)


    Manav
Re: Concerning hash operations (appending, concatenating)
by BUU (Prior) on Mar 24, 2005 at 21:09 UTC
    Duh, just use array_merge from "Array-PAT", which I've reproduced below for your convience:
    sub array_merge{ my(@array1, @array2) = @_; foreach my $element (@array2){ @array1 = (@array1, $element); }#foreach return @array1; }#array_merge

      Is this a joke BUU?

      Lets have a look at the code:

      my(@array1, @array2) = @_;

      So this line will magically split @_ into two parts, the first part will go into @array1 and contain the whole contents of @_, @array2 will be empty.

      foreach my $element (@array2){ @array1 = (@array1, $element); }#foreach

      Good thing this loop never executes as its a pretty inefficient way to add elements to an array. Perhaps the author has never heard of push.

      So this entire subroutine could be replaced by:

      sub array_merge{ @_ };
      ---
      demerphq

        Of course it's a joke.

        Good lord.
        Wow, that's actual code checked into CPAN.

        I guess it does what it says it does, anyways, just has some code that's never used.

        I guess I don't understand what it has to do with concatenating hashes??
        --------------
        "But what of all those sweet words you spoke in private?"
        "Oh that's just what we call pillow talk, baby, that's all."