Reputation: 148
Is there a way to do the following while using only one data structure?
my %hash = (
"key1" => "value1",
"key2" => "value2",
"key3" => $hash{key1},
);
So basically, I want to set a hash's key value to another key value. I've tried the above syntax but get the following warning:
Global symbol "%hash" requires explicit package name at ...
I'm assuming this is because I'm trying to reference the hash before it's actually been created.
Upvotes: 3
Views: 1620
Reputation: 1735
You could say
my %h;
my $i = 0;
while (
my ( $k, $v ) = (
key1 => '"value1"',
key2 => '"value2"',
key3 => '$h{key1}',
)[ $i, $i + 1 ]
)
{
$h{$k} = eval $v;
$i += 2;
}
as long as any keys referred to appear in the list before they are referenced.
Upvotes: 0
Reputation: 386696
%hash
doesn't contain anything when creating the list of values to assign to the hash, since you haven't assigned the list to the hash yet.
In fact, %hash
doesn't exist when creating the list of values to assign to the hash since assignments evaluate their RHS before their LHS. That's why you're getting a strict error.
Some solutions you might like:
my %hash = (
key1 => "value1",
key2 => "value2",
key3 => "value1",
);
my $value1 = "value1";
my %hash = (
key1 => $value1,
key2 => "value2",
key3 => $value1,
);
my %hash = (
( map { $_ => "value1" } qw( key1 key3 ) ),
key2 => "value2",
);
Upvotes: 1
Reputation: 29854
Why not make your own function:
use strict;
use warnings;
sub make_hash (@) {
my %h;
my @unresolved;
while ( @_ ) {
my ( $key, $value ) = splice( @_, 0, 2 );
next unless defined $value;
if ( not ref( $value )
and my ( $ref ) = $value =~ /^ref:\s*(.*\S)\s*$/
) {
if ( defined( my $v = $h{ $ref } )) {
$h{ $key } = $v;
}
else {
push @unresolved, [ $key, $ref ];
}
}
else {
$value =~ s/^lit://;
$h{ $key } = $value;
}
}
$h{ $_->[0] } = $h{ $_->[1] } foreach grep { exists $h{ $_->[0] }; } @unresolved;
return wantarray ? %h : \%h;
}
To demonstrate some of the power:
my %hash
= make_hash(
'key1' => 'value1'
, 'key2' => 'value2'
, 'key3' => 'ref:key1'
, 'key4' => 'lit:ref:key2'
, 'key5' => 'lit:lit:ref:key3'
);
The lit:
prefix covers the case of "What if I really wanted to pass a value that is a non-reference as 'ref:so-and-so'
? It also is recursive in answering, "What if I direly need to make a value 'lit:xzy'.
I've done this and I've also blessed a reference to a passed piece of data to a Lit
class or something along those lines.
sub Ref ($) { bless \shift, 'Ref' }
And then in the make_hash
routine you'd just check for ref( $value ) eq 'Ref'
. And specify it like the following:
my %hash
= make_hash(
'key1' => 'value1'
, 'key2' => 'value2'
, 'key3' => Ref 'key1'
);
There are many ways to make Perl act like you wish it did.
Upvotes: 1
Reputation: 129549
This is because, at the moment of using $hash{key1}
, the symbol "hash" was not yet added to the symbol table. Your package doesn't yet know what %hash
is, because it won't be added to the symbol table until AFTER the closing ")" is parsed - yet it will encounter the symbol 1 line earlier.
You need to pre-declare %hash in advance:
my %hash;
%hash = (
"key1" => "value1",
"key2" => "value2",
"key3" => $hash{key1},
);
However, while the above will compile, it will NOT work as expected, since nothing has been assigned to %hash
yet when $hash{key1}
is evaluated. You need to assign it first, as a separate statement:
my %hash;
%hash = (
"key1" => "value1",
"key2" => "value2",
);
%hash = %hash, (
"key3" => $hash{key1},
); # Or simply $hash{key3} = $hash{key1};
Please note that the above merely copies the value from key1
into key3
, once. They are in no way linked/associated after that. If you want to alias key1
and key3
(e.g. have them point to the same value, instead of contain a copy of the value), this is possible but not nearly as trivial. You need to make it into a tied hash and write custom tie handlers to get it right, or have each key's value be a reference.
Upvotes: 9
Reputation: 40152
You can use the core module Hash::Util
to access the low level hv_store
routine which can alias values of the hash together. It isn't quite as clean looking as using Data::Alias
but it is already installed.
use Hash::Util 'hv_store';
my %hash = (
key1 => "value1",
key2 => "value2",
);
hv_store %hash, key3 => $hash{key1};
say "$_: $hash{$_}" for sort keys %hash;
# key1: value1
# key2: value2
# key3: value1
$hash{key1} = 'new value';
say "$_: $hash{$_}" for sort keys %hash;
# key1: new value
# key2: value2
# key3: new value
if you dont want the aliasing to persist past the initial assignment, then you can just write the line:
$hash{key3} = $hash{key1}
after your initial declaration and skip using hv_store
.
Upvotes: 2
Reputation: 2857
If you want $hash{key1} to be an alias of $hash{key3}, then this can be accomplished easily using Data::Alias.
use Data::Alias;
my %hash = (
key1 => 'value1',
key2 => 'value2'
);
alias $hash{key3} = $hash{key1};
$hash{key1} and $hash{key3} will now refer to a single value, and updates to one will be visible in the other.
Upvotes: 4