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

I was playing with this code which swaps hash values:
use strict; my %hash = ( 1 => 'angel', 2 => 'buffy', 3 => 'cordelia', 4 => 'dawn', 5 => 'ethan', 6 => 'faith', 7 => 'giles' ); reorder(5,'up'); # ethan moves up: swaps place with dawn display(); sub reorder(){ my ($item,$direction) = @_; if($direction eq 'up'){ # coming soon -- 'down' my $tmp = $hash{$item}; # save ethan $hash{$item} = $hash{ ($item -1) }; # move dawn down to 5 $hash{ ($item -1) } = $tmp; # put ethan back as 4 } } sub display { print '-' x 30,"\n"; foreach my $key (sort (keys (%hash))) { printf("%12s is now number %d\n",$hash{$key},$key); } print '-' x 30,"\n"; }

Which works just fine, but then I thought, I wonder if perl can just swap the values for me and changed the reorder sub to this:

sub reorder(){ my ($item,$direction) = @_; if($direction eq 'up'){ $hash{$item,($item -1)} = $hash{ ($item -1), $item}; # maybe perl can automagically do the $tmp thing for me? } }

but when I ran it that way, I got this output:

------------------------------ angel is now number 1 buffy is now number 2 cordelia is now number 3 dawn is now number 4 ethan is now number 5 is now number 5 faith is now number 6 giles is now number 7 ------------------------------

Which doesn't make sense to me. I now have two number fives in my hash? That's not possible!

I know there's something fundamentally illogical about trying to order hashes when I could be using arrays, but leaving that aside, what happened here?
--

“Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
M-J D

Replies are listed 'Best First'.
Re: Swapping Two Hash Values
by Zaxo (Archbishop) on May 28, 2003 at 00:35 UTC

    The simplest way I know to swap values for hash keys is with a slice: @hash{ 'foo', 'bar' } = @hash{ 'bar', 'foo'};

    After Compline,
    Zaxo

      The simplest way I know to swap values for hash keys is with a slice:

      That's what he tried to do. He just got the syntax wrong.

      Cody, instead of

      $hash{$item,($item -1)} = $hash{ ($item -1), $item};
      try
      @hash{$item, ($item - 1)} = @hash{($item - 1), $item};
      Pay careful attention to the sigils. You have to use an @ to specify a slice.

      -sauoq
      "My two cents aren't worth a dime.";
      
Re: Swapping Two Hash Values
by BrowserUk (Patriarch) on May 28, 2003 at 00:51 UTC

    If your keys are always numeric, the you shoudln't be using a hash. That's what arrays are for...:)

    Notice thta this way, your don't need to sort in display().

    use strict; my @array = ( 'angel', 'buffy', 'cordelia', 'dawn', 'ethan', 'faith', 'giles', ); reorder(5,'up'); # ethan moves up: swaps place with dawn display(); sub reorder { my ($item,$direction) = @_; ### Adjust index as arrays start at 0 $item--; # Down is here :) - you should add bounds checking! my $other = $direction eq 'up' ? $item - 1 : $item + 1; @array[$item, $other] = @array[$other, $item]; return; } sub display { print '-' x 30,"\n"; foreach my $key (0..$#array) { printf("%12s is now number %d\n",$array[$key],$key+1); ##Adjust key +for display. } print '-' x 30,"\n"; } __END__ D:\Perl\test>test ------------------------------ angel is now number 1 buffy is now number 2 cordelia is now number 3 ethan is now number 4 dawn is now number 5 faith is now number 6 giles is now number 7 ------------------------------
Re: Swapping Two Hash Values
by sauoq (Abbot) on May 28, 2003 at 00:46 UTC

    I think someone has to ask: why aren't you using an array for this? You are basically implementing an array on top of a hash anyway by using the hash keys as indexes.

    -sauoq
    "My two cents aren't worth a dime.";
    
      I'm not using an array because I'm not very smart, but thanks for pointing it out.

      I was just pondering data structures, really. If I had some much more complex structure, possibly read out of XML in such a way that it just had to be a hash, I might have wanted to deal with

      %hash{ array => [foo,bar,baz], bunchofotherstuff => [array [hash{ array[ ] } ], order => 1 }

      But don't I get a point for knowing it was stupid? More importantly, I've grasped something about playing with hash values, and I've proved that my instincts about perl having a smart way to do stuff like that were right.
      --

      “Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
      M-J D
Re: Swapping Two Hash Values
by Enlil (Parson) on May 28, 2003 at 00:39 UTC
    maybe a little off topic, but you could replace the reorder sub with something as simple as this:
    sub reorder { my ($item,$direction) = @_; if($direction eq 'up'){ @hash{$item,$item-1} = @hash{$item-1,$item}; } }
    which is easier on the eyes, and no temp variables.

    -enlil

      @hash{$item,$item-1} = @hash{$item-1,$item};

      Aha! Now I see what I was doing wrong -- I had

      $hash{$item,$item-1} = $hash{$item-1,$item};
      

      So I probably created another key which is not 5 but 5-undef or something?

      Thank you both very much.
      --

      “Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
      M-J D
Re: Swapping Two Hash Values
by edoc (Chaplain) on May 28, 2003 at 00:47 UTC

    Yup, you're correct, you can't have duplicate keys.. and you haven't.. try this..

    sub display { print '-' x 30,"\n"; foreach my $key (sort (keys (%hash))) { printf("%12s is now number %d\n",$hash{$key},$key); } print '-' x 30,"\n"; foreach my $key (sort keys %hash) { print "$key = $hash{$key}\n"; } print '-' x 30,"\n"; } __END__ prints: ------------------------------ angel is now number 1 buffy is now number 2 cordelia is now number 3 dawn is now number 4 ethan is now number 5 is now number 5 faith is now number 6 giles is now number 7 ------------------------------ 1 = angel 2 = buffy 3 = cordelia 4 = dawn 5 = ethan 54 = 6 = faith 7 = giles ------------------------------

    cheers,

    J

Re: Swapping Two Hash Values
by halley (Prior) on May 28, 2003 at 00:30 UTC

    Hashes can have duplicate VALUES, but not duplicate KEYS. You assigned a value to the key of '', probably because your assignment statement's left side's key expression evaluated to undef. (Undef stringizes as ''.)

    I shouldn't scan so quickly, the odd values-first printout threw me off. However, this should explain your odd "duplicate keys" mystery.

    %hash = ( '1' => 'angel', '2' => 'buffy', '3' => 'cordelia', '4' => 'dawn', '5' => 'ethan' '54' => undef, '6' => 'faith', '7' => 'giles', );

    The printf "%d" turned the key '5 4' into an integer '5'.

    --
    [ e d @ h a l l e y . c c ]

      You assigned a value to the key of ''
      Huh? I'm really confused now. The key is the number, not the name. That display says the value is undef and the key is 5.
      --
      “Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
      M-J D

        Ed got this one wrong, Cody. Listen to Zaxo.

        -sauoq
        "My two cents aren't worth a dime.";
        
Re: Swapping Two Hash Values
by Aristotle (Chancellor) on May 29, 2003 at 22:54 UTC
    To find out about the reason you got these strange keys, read perlvar - look at the section about $;. You stumbled over the old multidimensional array emulation feature.

    Makeshifts last the longest.