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

I thought I saw this question answered here already, but I can't find it.

Is it possible to use the same key twice in a hash?
I have a hash that will be three dimensional:
$myhash{$level1}{$level2} = 3;
For every $level1 there will be multiple $level2 keys, the problem is there may be multiple $level2 keys with the same name.
Can this be handled in a hash?
I was thinking of testing for uniqueness and if the keys are the same, putting the keys in an array and putting the array in the hash, but that will make comparisons more difficult and eliminate most of the value of using a hash in the first place.
Any ideas?

Your humble servant,
-Chuck

Replies are listed 'Best First'.
RE: Multiple identical keys in a hash.
by Aighearach (Initiate) on Apr 24, 2000 at 20:02 UTC

    I might be misunderstanding the question, but it sounds like you asking if you can assign $hash{'key'} = "something"; and then later assign  $hash{'key'} = "something_else";

    This is not possible. You could put it in an array if it already exists, like $hash{'key'} = ( exists $hash{'key'} ) ? [ $hash{'key'}, "more" ] : "else"; but then you have to test each value before you use it. Unless, you just always put it in an array, ie $hash{'key'} = ( exists $hash{'key'} ) ? [ $hash{'key'}, "more" ] : ["else"];

    Hope that wasn't too far off from the question...

    --4c6966653a205468652073656172636820666f
      7220746861742070657266656374204765656b
      20436869632c20746865206f6e652077697468
      2074686520737061726b6c696e672065796573
      2077686f20706c617973206973207261696e2e
    
Re: Multiple identical keys in a hash.
by BBQ (Curate) on Apr 24, 2000 at 17:11 UTC
    I'm not sure I understood what the problem is, but can't you simply test on the keys with the same varname? i.e.:
    if (defined $hash{$key}{$key}) { # do your thing }
    Would that work? I used defined in this case because the value might be null, and that's not really what you're testing for, is it? You might still have the two levels assigned even if they do have a null value.

    Spank me if I'm wrong...

    #!/home/bbq/bin/perl
    # Trust no1!
      My data is similar to:

      File 1

      dn=machinename
      config=2000
      speed=19200
      setting=value1
      setting=value2
      setting=value3

      File 2

      dn=machinename2
      config=2020
      speed=9600
      setting=value1
      setting=value2
      setting=value3


      I need for all occurances of setting to end up in the hash for file one, and then compare the contents of file two and output the differences.
        If the machines have different names (assuming a normal network :o)), don't you just need to do this? (continued from ZZamboni's code)
        foreach $f (@files) { open FILE, $f or die "Error: $!"; while(<FILE>) { chomp; my ($k,$v)=split("=",$_,2); # Assume dn= comes before other lines if ($k eq "dn") { push(@machines,$k); $machine = $k; } else { push(@{$machine}{$k},$v); } } close FILE; }
        In this case you would end up with almost the same effect:
        @machines = ('machinename','machinename2'); ${'machinename'}{'config'} = 2000; ${'machinename'}{'speed'} = 19600; ${'machinename'}{'setting'} = value1, value2, value3; ${'machinename2'}{'config'} = 2020; ${'machinename2'}{'speed'} = 19600; ${'machinename2'}{'setting'} = value1, value2, value3; etc...
        Points being:
        1. do you have to name a hash?
        2. is it better to have @machines?

        I'm starting to wonder about this too...
        You could put all the values in array references indexed by your two-level hash. Something like this:
        foreach $f (@files) { open FILE, $f or die "Error: $!"; while(<FILE>) { chomp; my ($k,$v)=split("=",$_,2); # Assume dn= comes before other lines $level1=$k, next if $k eq "dn"; unless ($level1) { die "Need to get dn= line first\n"; } push @{$hash{$level1}{$k}}, $v; } close FILE; }
        Then you will have something like this:
        $hash{machinename}{config}=["2000"] $hash{machinename}{speed}=["19200"] $hash{machinename}{setting}=["value1", "value2", "value3"] $hash{machinename2}{config}=["2020"] etc.
        And then do comparisons or anything else you need. See chapter 4 of The Perl Cookbook for examples of how to do fancy array operations, including comparisons.
      I forgot to say, the value of dn will be the $level1 key and all of the other name=value pairs will be $level2 key = value.

      Your humble servant,
      -Chuck
        So do you want a entry like:
        dn=machinename1
        name=value
        
        to yield:
        $hash{"machinename1"}{"name"}="value";
        
        or
        $hash{"machinename1"}{"name=value"}=1;
        
        in case 1 you should save the different values for name in an array. something like this should work
        foreach(@files){
            my $dn = "";
            open FILE, "$_" or die "error opening $_: ".$!;
            foreach(<FILE>){
        	chomp;
                $dn = $1 if(/dn=(.*)/);
                push @{$hash{$dn}{$1}}, $2 if(/(^=*)=(.*)/ && $dn ne "");
            }
            close FILE;
        }
        
        if it is case2, I don't know why you would need to add the same line more than once. if you need the number of occurances try :
         
        foreach(<FILE>){
            chomp;
            $dn = $1 if(/dn=(.*)/);
            $hash{$dn}{$_}++ if(/=)/ && $dn ne ""); 
        }
        
        for the inner loop. or use an array for the "name=value" entries:
        foreach(<FILE>){
            chomp;
            $dn = $1 if(/dn=(.*)/);
            push @{$hash{$dn}}, $_ if(/=/ && $dn ne "");
        }
        
        hope that helps.