简体   繁体   中英

Hashing multiple values to a key in perl

I'm reading in a file of words and need to hash words to a key if they are anagrams. So if I read in dog, I sort the word to become dgo. And this would be my key. So I read in the word god, it would be sorted to dgo as well and they should both hash to the same key.

Here is what I am trying but I am not sure if I am doing this correctly.

    if(exists $hash{$string})
    {
            @values2 = $hash{$string};
            push @values2, $original; 

            for my $word (@values2)
            {
                print $word."\n";
            }
            #print "Hello";
    } 

    else
    {
         @values = ();
        $hash {$string} = @values;  
        push @values, $string; 
    }   
}

So the $string is my sorted word, the key. So if the key doesn't exist, i create a new array at that key for my $hash. i then push the orginal word into the array. But if the key already exists, I then get the array from the hash and push or add the next word.

But this isn't working properly. Can I not do this?

The thing you need to understand is: There is no such thing as a two dimensional data structure in perl. You don't have a hash of arrays, you have a hash of array references .

This can lead to some pretty subtle gotchas.

This for example - isn't doing what you think:

@values = ();
$hash{$string} = @values;
push @values, $string;

It's emptying @values . But then it's assigning it in a scalar context. Which means you're setting:

$hash{$string} = 0; 

And then inserting $string into @values , but that's making no difference to the hash, because you've set the hash value to the size of your empty array.

Likewise this:

@values2 = $hash{$string};
push @values2, $original;

for my $word (@values2) {
    print $word. "\n";
}

You're only ever going to be retrieving an array reference at best (but if you've populated with the else block it's not even that - it's just 0 ) which means your for loop isn't going to work. $hash{$key} can only ever be a single value.

If you want to set a hash key to an array;

 $hash{$string} = [@values]; 

If you want to add elements:

 push ( @{$hash{$string}}, @values ); 

And if you want to extract elements;

my @array = @{ $hash{$string} }; 

You need the extra sigil, because that's how you tell perl 'work with a reference'. ( You can also use -> notation in some circumstances. I've omitted this to avoid confusing the issue )

Yes, you can do this. But you cannot store an array in a hash, you have to store a reference to it.

push @{ $hash{$string} }, $original;

To retrieve the array, derefence the value:

print join ' ', @{ $hash{dgo} }; # dog god

Basic Perl data structures are about single values. The variable $foo can only store a single value. The array @foo can store an array of single values. The hash %foo has a key that points to a single value.

If you need more (such as a key that points to multiple values), you need to know about Perl References . A Perl reference is a Perl data structure (such as a hash or array) where each entry points not to a single value, but to another structure.

In your case, you'd like your key (the word dgo ) to point to an array of words that have those letters.

Imagine something like this:

my @dgo_words = qw(dog dgo god gdo odg ogd);   # All possible combinations
$words{dgo} = \@dgo_words;   # The '\' means this is a reference to @dgo_words

Now, words{dgo} points to a reference to the array @dgo_words . If dereference the reference (by putting the correct prefix on the variable), I can get back to the array:

my @array_of_dgo_words = @{ $words{dgo} };

Remember, $words{dgo} points to an array, and putting the @ in front gives me access to that array. The curly braces in this particular case are optional:

my @array_of_dgo_words = @$words{dgo};

Personally, I prefer the braces because it highlights the fact this is a reference. Others feel that eliminating them makes the code easier to read.

If @{ $words{dgo} } is my array, I could use push to add words to the array:

push @{ $words{dgo} }, 'dog';

Now, dog is added to the array referenced by $words{dgo} .

Here's a simple program:

#! /usr/bin/env perl

use strict;
use warnings;
use feature qw(say);


my %words;
#
# First add all the words into the correct key
#
while ( my $word = <DATA> ) {
    chomp $word;
    my $key = join '', sort split //, $word;
    push @{ $words{$key} }, $word;
}

for my $group ( sort keys %words ) {        # For each key in my word hash
    my @word_group = @{ $words{$group} };   # Dereference to get the list of words
    say qq(Words for group "'$group":);
    for my $word ( @word_group ) {          # This loop prints out the words
        say "    $word";
    }
}

__DATA__
dog
bog
save
god
gob
vase

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM