Reputation: 501
I wanted to have a hash of arrays of counters, with initial values of 0. Here is my first attempt at the code:
my @names = ("", a, b, c);
my %hsh = ();
for $i (1..3)
{
# I expected this line to give me a fresh new array each time - it did not.
$hsh{$names[$i]} = (0, 0, 0); # Assignment line #
doprint($i); #see below
}
#output is: ARRAY(0x1501128) ARRAY(0x1501128) ARRAY(0x1501128)
We see they are all the same array, which is not desired. To fix the problem, I changed the # Assignment line to this:
$hsh{$names[$i]} = ( );
push(@{$hash{$names[$i]}}, 0, 0, 0);
# output is: ARRAY(0x1510c70) ARRAY(0x1510d18) ARRAY(0x1510dc0)
Now they are all different arrays, and life is good.
If the Assignment line uses (0, 0, $i) I get different arrays (at least here - they might be reused elsewhere)
but I couldn't fool it by using (0, 0, $i - $i) or a more complex formula to generate a 0 value.
Is this some sort of optimization by Perl? Why isn't (0, 0, 0) a new array each time? Would (0, 0) or (0, 0, 0, 0) be different?
For the sake of completeness, here is the print subroutine:
sub doprint
{
my $i = shift;
print (\@{$hsh{$names[$i]}}, " ");
}
---- The following edit is on the morning of Feb 9 after the "there is no array comment" - I wanted to add some code and comments don't allow that very well.
OK: $h{$i} = (0, 0, 0); initializes it to a scalar value of 0.
But, as indicated in the output below, there's an array somewhere.
Did writing $h{$i}[$i] create @-something alongside the scalars?
my %h = ();
sub dotest
{
my $desc = shift;
print " # $desc\n";
for (0, 1, 2)
{
$h{$_}[$_] += 1 + $_;
}
for (0, 1, 2)
{
print " # $_: $h{$_} ";
print "# @{$h{$_}}" . " # $h{$_}[$_] \n";
}
print "\n";
}
%h = ( );
$h{0} = [0, 0, 0];
$h{1} = [0, 0, 0];
$h{2} = [0, 0, 0];
dotest("Initialize to [0, 0, 0]");
%h = ( );
$h{0} = (0, 0, 0);
$h{1} = (0, 0, 0);
$h{2} = (0, 0, 0);
dotest("Initialize to (0,0,0)");
%h = ( );
dotest("No initialization");
%h = ( );
$h{0} = \@{(0, 0, 0)};
$h{1} = \@{(0, 0, 0)};
$h{2} = \@{(0, 0, 0)};
dotest("Initialize to \\\@{(0, 0, 0)}");
# Initialize to [0, 0, 0] - Separate arrays, initialized to zeros.
# 0: ARRAY(0x10ca5e0) # 1 0 0 # 1
# 1: ARRAY(0x10ca778) # 0 2 0 # 2
# 2: ARRAY(0x2709da0) # 0 0 3 # 3
# Initialize to (0,0,0) - scalars AND a shared array?
# 0: 0 # 1 2 3 # 1
# 1: 0 # 1 2 3 # 2
# 2: 0 # 1 2 3 # 3
# No initialization - separate arrays, initialized to null
# 0: ARRAY(0x2709e48) # 1 # 1
# 1: ARRAY(0x10ca718) # 2 # 2
# 2: ARRAY(0x2709da0) # 3 # 3
# Initialize to \@{(0, 0, 0)} - reusing the shared array from previous?
# 0: ARRAY(0x2709d88) # 2 4 6 # 2
# 1: ARRAY(0x2709d88) # 2 4 6 # 4
# 2: ARRAY(0x2709d88) # 2 4 6 # 6
Upvotes: 0
Views: 160
Reputation: 132896
Dave Cross has already showed you how to do what you wanted to accomplish. But, here are the gory details.
An assignment to a scalar is in scalar context, and a single-element access to a hash is a scalar. So, this is in scalar context:
$hash{key} = ( 0, 0, 0 );
The operator on the right hand side of the assignment is the comma. In scalar context, the comma operator evaluates its left hand side then discards the results. It evaluates the right hand side and returns that. See perlfaq4 where I also explain this. It's a feature from C and shows up in several other languages.
It's easier to see when the values aren't special:
$hash{key} = ( 9, 56, 137 );
So, start evaluating the right side in scalar context:
$hash{key} = ( 9, 56, 137 );
$hash{key} = ( ( 9, 56 ), 137 );
$hash{key} = ( 56, 137 );
$hash{key} = ( 137 );
You end up with the result of the rightmost evaluation. That's the same as:
$hash{key} = 137;
Some people (ab)use this to combine two statements that would otherwise have to be on separate lines (not in Perl, really, but others, maybe JavaScript):
my $foo = ( ($sideeffect=$a), ($b = $c) );
Or, you can combine expressions in places where you normally only use one (although the who thing is an expression):
for( my $i = 0; $i < 10; $j++, $i++ ) { ... }
Now, there's the second part of the problem. You have a non-reference value for that key, then you try to dereference it to use the array in push
. That doesn't work because it was never a reference.
Upvotes: 3
Reputation: 69314
This line doesn't do what you think:
$hsh{$names[$i]} = (0, 0, 0);
(0, 0, 0)
is not an array, it's a list. And you can't store an array (or, indeed, a list) in a hash element. You can only store scalar values in a hash.
You can get round this by storing a reference to an array in your hash. And the easiest way to do that is to use the anonymous array constructor ([ ... ]
).
$hsh{$names[$i]} = [0, 0, 0];
Upvotes: 2