Steve Dickinson
Steve Dickinson

Reputation: 148

Defining a hash value using another hash value.

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

Answers (6)

MisterEd
MisterEd

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

ikegami
ikegami

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

Axeman
Axeman

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

DVK
DVK

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

Eric Strom
Eric Strom

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

zgpmax
zgpmax

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

Related Questions