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

I want to use an array for the keys of a hash, and I want that array to point at another array as the values in the hash, something like the following:
my @multiKey1 = ("a","b"); my @stuff1 = ("1", "2", "3"); my @multiKey2 = ("c","d"); my @stuff2 = ("4", "5"); my @multiKey3 = ("e","f"); my @stuff3 = ("6", "7"); my %testHash = ( \@multiKey1 => \@stuff1, \@multiKey2 => \@stuff2, \@m +ultiKey3 => \@stuff3);
The problem right now is, although I can get access to the arrays again if I access a specific value with a specific key, I can't get access when I just try keys() to start out. E.g.
@testHashKeys = keys %testHash; @testHashValues = values %testHash; $tmp = @testHashKeys; foreach (@testHashKeys){ print "deref bla = @$_ = $_\n"; } foreach (@testHashValues){ print "deref bla = @$_ = $_\n"; } $ref = $testHash{$testHashKeys[0]}; @valueArray = @$ref; print "valueArray = @valueArray\n";
For this, I get the following output, and I don't understand why :-/
deref bla = = ARRAY(0x10082add0) deref bla = = ARRAY(0x10082afc8) deref bla = = ARRAY(0x10082aed8) deref bla = 1 2 3 = ARRAY(0x10082ae48) deref bla = 6 7 = ARRAY(0x10082b040) deref bla = 4 5 = ARRAY(0x10082af50) valueArray = 1 2 3
The key thing is the line: print "deref bla = @$_ = $_\n"; If it knows that $_ is an array reference, why doesn't @$_ dereference it to give me the array values printed out, like everywhere says it should, and like happens with the values array? I greatly appreciate you taking the time to read this :)

Replies are listed 'Best First'.
Re: reference as hash keys and values
by muba (Priest) on Jul 07, 2011 at 00:38 UTC

    Good question. Let's investigate.

    # After this line from snippet 1... my %testHash = ( \@multiKey1 => \@stuff1, \@multiKey2 => \@stuff2, \@m +ultiKey3 => \@stuff3); # ... add this: use Data::Dumper; print Dumper \%testHash; # And run the snippet. __END__ Output: $VAR1 = { 'ARRAY(0x98a9b4)' => [ '6', '7' ], ... ...

    Looks like the array ref for the key has been turned into a string. I'm a little foggy on the details, but from the top of my head I seem to remember that the => operator converts whatever is on it's left hand to a string. So here's an idea: let's not use the fat comma and see what happens next.

    # Change this line from your first snippet... # my %testHash = ( \@multiKey1 => \@stuff1, \@multiKey2 => \@stuff2, \ +@multiKey3 => \@stuff3); # into this: my %testHash = ( \@multiKey1, \@stuff1, \@multiKey2, \@stuff2, \@mult +iKey3, \@stuff3); # ... and still use this: use Data::Dumper; print Dumper \%testHash; # And run the code.

    Gah, no luck. Output remains the same. So it isn't the fat comma's fault. Of course, it could still be that Data::Dumper is messing things up, so let's remove those two lines and let's just check wether perl thinks the keys are actually references. ref() is a nice function that tells us what data type is behind a reference.

    my %testHash = ( \@multiKey1, \@stuff1, \@multiKey2, \@stuff2, \@mult +iKey3, \@stuff3); @testHashKeys = keys %testHash; @testHashValues = values %testHash; $tmp = @testHashKeys; foreach (@testHashKeys){ print ref($_), ": "; print "deref bla = @$_ = $_\n"; } foreach (@testHashValues){ print ref($_), ": "; print "deref bla = @$_ = $_\n"; } __END__ Output: : deref bla = = ARRAY(0x98a9b4) : deref bla = = ARRAY(0x98a914) : deref bla = = ARRAY(0x98a844) ARRAY: deref bla = 6 7 = ARRAY(0x98aa04) ARRAY: deref bla = 4 5 = ARRAY(0x98a964) ARRAY: deref bla = 1 2 3 = ARRAY(0x3e8d8c)

    So what it looks like, to me, is that hash keys can't hold references. I could be wrong, of course, but quite clearly in this case the references you specified as keys were turned into just strings. I'm curious to what others have to say, though - I can't quite believe one can't use a reference as a key, given that refs are just scalars anyway.

      Good process of testing and deduction. Deductive reasoning like that is one of those qualities present in programmers who are capable of solving problems on their own. I believe logical and deductive reasoning is an attribute that must accompany success as a programmer. Your testing can be summarized well by perlref:

      WARNING

      You may not (usefully) use a reference as the key to a hash. It will be converted into a string:

      $x{ \$a } = $a;

      If you try to dereference the key, it won't do a hard dereference, and you won't accomplish what you're attempting. You might want to do something more like

      $r = \@a; $x{ $r } = $r;

      And then at least you can use the values(), which will be real refs, instead of the keys(), which won't.

      The standard Tie::RefHash module provides a convenient workaround to this.

      References are passed around as scalars, but keys are indices composed of string values, not full scalar citizens.


      Dave

      muba:

      Nice post: ++. Experimentation is a good way to test your theories. Of course, digging around the documentation can also be useful. Glancing through perldoc perldata yields:

      Perl has three built-in data types: scalars, arrays of scalars, and
      associative arrays of scalars, known as "hashes". A scalar is a single
      string (of any size, limited only by the available memory), number, or
      a reference to something (which will be discussed in perlref). Normal
      arrays are ordered lists of scalars indexed by number, starting with 0.
      Hashes are unordered collections of scalar values indexed by their
      associated string key.

      ...roboticus

      When your only tool is a hammer, all problems look like your thumb.

Re: reference as hash keys and values
by ikegami (Patriarch) on Jul 07, 2011 at 00:43 UTC

    Hash keys must be strings, although Tie::RefHash uses magic to provide what looks to be a hash that accepts non-strings as keys.

    I can get access to the arrays again if I access a specific value with a specific key

    Yes and no.

    This works:

    my $k = [qw( a b )]; $h{$k} = $v; $v = $h{$k};

    This doesn't (except occasionally by fluke):

    $h{ [qw( a b )] } = $v; $v = $h{ [qw( a b )] };

    You're using the address of the array as the key, so if you create a similar array at a different address, it won't be the same key.

Re: reference as hash keys and values
by locked_user sundialsvc4 (Abbot) on Jul 07, 2011 at 02:32 UTC

    Certainly, one argument against using the notion of using references as hash-keys is that ... references can change.   In the general case, what would you do if the hash-key is a reference to “something” that, i.e. through another extant reference to the same “something,” suddenly changes its spots.

    If you need to refer to an object in this way, it might be okay to explicitly serialize the object into a string form, e.g, YAML or even JSON, then use that string as the key.   (Presumably, this is more-or-less what the previously mentioned Tie module is doing ...)   But, whatever you wind up doing, I advocate doing it very explicitly.   (Consider defining a Perl class with appropriate methods... so “the right thing” is always done, and you only have to specify whatever “the right thing” is, once.)

Re: reference as hash keys and values
by CountZero (Bishop) on Jul 07, 2011 at 15:42 UTC
    An ordinary hash key is one single string. A "multiple key" hash key does not exist.

    Of course you can combine the elements of a multiple key into one string, but that only proves that the key to a hash is a single string.

    Another solution is to spread out the elements of your multiple keys over different levels of the hash.

    Something like this:

    my %hash = ( a => { b => [1, 2, 3], c => [4, 5, 6], }, b => { d => [7, 8, 9], e => [10, 11, 12], }, );
    $hash{a}->{b} will then return the arrayref to the array 1, 2, 3, and so on.

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

      Count Zero the issue with combining the elements in such a way is that for each key withn the multiple key set you are producing different arrays. In the op example it is that both keys within the multikey refer to array [1,2,3]. So in your example hash b and c should both hold [1,2,3].

      Could we combine the key elements like you suggest but use input processing to determine when either of the key conditions has been met for the required array.

      my %hash = ( { ab => [1, 2, 3], cd => [4, 5, 6], }, ); print $hash{'cd'} if /c|d/;
      the dereferencing syntax may be incorrect here but the example is for the processing of the key.
Re: reference as hash keys and values
by armstd (Friar) on Jul 11, 2011 at 05:08 UTC

    As already pointed out, references cannot effectively be used as hash keys.

    I eventually came up with a use case once, where I really wanted to be able to identify my objects uniquely, for building Sets of unique objects and such. With a tip from a friend, I came up with the following scheme. Instead of using the reference itself, use a reliable string representation of the reference. Scalar::Util::refaddr() can provide such, for a single-process oriented use-case anyway.

    my $uniqueID = Scalar::Util::refaddr( $self ); $objectsHash->{$uniqueID} = $self;

    From then on, given any structure using those IDs as hash keys, you can retrieve the referenced structure/object from the $objectsHash index. Inside-out Objects use essentially the same means for a slightly different purpose. Track a unique objectID, not the object itself.

    In my ideal world, concrete objects provide the override to identify themselves, but my abstract classes still want to be able to do something in the absence of that override.

    --Dave