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."
Re: Concerning hash operations (appending, concatenating)
by Zaxo (Archbishop) on Mar 24, 2005 at 16:31 UTC
|
%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.
| [reply] [d/l] |
|
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
| [reply] |
|
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
| [reply] [d/l] [select] |
|
|
|
|
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."
| [reply] [d/l] |
|
<%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.
| [reply] [d/l] [select] |
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];
| [reply] [d/l] |
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' );
| [reply] [d/l] |
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. | [reply] [d/l] |
|
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;
| [reply] [d/l] [select] |
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
| [reply] [d/l] [select] |
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
| [reply] [d/l] |
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)
| [reply] [d/l] [select] |
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
| [reply] [d/l] |
|
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{ @_ };
| [reply] [d/l] [select] |
|
Of course it's a joke.
Good lord.
| [reply] |
|
| [reply] |
|
|
|