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

I have two arrays. I wish one array to become to the keys to a hash, the other array to become the values to the hash. I want $array[5] to be paired in the hash with $arraytwo[5]. The closest I could get was by using a hash slice,

my %hash; @hash{@array}=%hash;
Which set @array to be keys in %hash. I don't know how to set @arraytwo to be values in %hash. Thanks

Update: Ok, thanks very much, the @hash{@array} = @arraytwo; hashslice worked fine. The possibility of duplicates occurs in only array, so I guess I should have that array as the values, to avoid problems.

Edit: BazB added extra code tags.

Replies are listed 'Best First'.
Re: Two arrays. One hash.
by Zaxo (Archbishop) on Nov 29, 2003 at 22:19 UTC

    You just didn't do the hash slice correctly,

    my %hash; @hash{@array} = @arraytwo;

    After Compline,
    Zaxo

Re: Two arrays. One hash.
by Limbic~Region (Chancellor) on Nov 29, 2003 at 22:25 UTC
    matt me,
    You might be on the right track with the hash slice. Consider the following:
    #!/usr/bin/perl -w use strict; my @keys = qw(one two three four five); my @vals = ( 1 .. 5 ); my %hash; @hash{ @keys } = @vals; print $_, " : ", $hash{$_}, $/ for keys %hash;
    The problem is doing the right thing if the arrays are not the same size, contain duplicates, or if they contain refs. Consider the following:
    my $smaller = @array1 > @array2 ? @array2 : @array1; for my $index ( 0 .. $smaller ) { my ($key, $val) = ($array1[$index] , $array2[$index]); if ( ! ref $key ) { if ( exists $hash{$key} ) { if ( ref $hash{$key} ) { push @{ $hash{$key} } , $val; } else { $hash{$key} = [ $hash{$key} , $val ]; } } else { $hash{ $array1[$index] } = $array2[$index]; } } }
    Of course, "the right thing" is subjective.

    Cheers - L~R
    Updated: Added duplicates pointed out by davido

Re: Two arrays. One hash.
by davido (Cardinal) on Nov 29, 2003 at 22:40 UTC
    Zaxo and Limbic~Region showed you how to do it with a hash slice, and that's definately the most efficient mechanism.

    Just be careful to avoid this sort of situation:

    my @keyset = qw/this repeat that repeat/ my @valset = qw/jerry george elaine kramer/; my %hash; @hash{@keyset} = @valset; print "$_: $hash{$_}\n" foreach keys %hash; __OUTPUT__ that: elaine this: jerry repeat: kramer

    For nonunique keys, only the last item assigned to a given nonunique key will be preserved. Poor 'george' was discarded.


    Dave


    "If I had my life to live over again, I'd be a plumber." -- Albert Einstein
      Just to add to davido's comment - would be nice to implement an array duplicated element detection mechanizm. Instead of assuming that the key array does not have duplicates, you might as well test for it. ;-)

      use strict; my @keyset = qw/this repeat that repeat/; my @valset = qw/jerry george elaine kramer/; print "\@keyset has ", hasdup(\@keyset) ? "duplicates (warning)" : "no duplicates", "\n"; print "\@valset has ", hasdup(\@valset) ? "duplicates" : "no duplicates", "\n"; sub hasdup { my $array = shift; # get the index of the elements in the uniq array my $last = $#{[keys %{{ map {$_ => 1} @$array }}]}; return $#{@$array} != $last; }
      And the output -
      @keyset has duplicates (warning) @valset has no duplicates

        And just to add to Roger's comment ... ;)

        I never use a hash slice unless i know that the array containing the keys has no duplicates. Now, i know that sometimes this just isn't practical, but i really only use hash slices when i know what the keys are going to be -- that is, i don't use them when i don't know what the keys are going to be. Besides, 99% of the time, you see the creating of a hash slice like so:

        my %month; my @month = qw(Jan Feb Mar Apr May June July Aug Sept Oct Nov Dec); @month{0..$#month} = @month;
        ... that is, you declare the keys and the values. As long as this is true, you shouldn't have to worry about writing code to catch duplicates. On the other hand, something like:
        print "Enter keys (sep with space): "; chomp (my $key = <STDIN>); print "Enter vals (sep with space): "; chomp (my $val = <STDIN>); my %hash; @hash{ split '\s',$key } = split '\s',$val;
        is just asking for trouble. I would instead (sigh) explicitly code out a "standard" for loop to ensure that duplicates were caught, as well as a mismatching number of keys and vals.

        jeffa

        L-LL-L--L-LL-L--L-LL-L--
        -R--R-RR-R--R-RR-R--R-RR
        B--B--B--B--B--B--B--B--
        H---H---H---H---H---H---
        (the triplet paradiddle with high-hat)
        
Re: Two arrays. One hash.
by Aristotle (Chancellor) on Nov 30, 2003 at 18:56 UTC
    Just for TMTOWTDI, you could also write a function to zip the arrays:
    sub zip { $#_ += @_ % 2; # force even nr of elements @_[ map +( $_, $_ + @_ / 2 ), 0 .. $#_ / 2 ] }
    and then simply assign them to the hash:
    my %hash = zip(@keys, @values);
    This is less efficient, but unlike the hash slice solution it can easily be used in the middle of a long expression.

    Makeshifts last the longest.