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

hi
just another perl newbie here
I need to interchange values of three variables $a,$b,$c
eg if $a=1,$b=2,$c=3;then at the end of the exexcution
$a=2;$b=3,$c=1.
Now , i know that i can do it like this very easily
($a,$b,$c)=($b,$c,$a)
but is there any other way to do it ?
i thought of this
$a=$a+$b+$c
$c=$a-($b+$c)
$b=$a-($b+$c)
$a=$a-($b+$c)
Is there any more ways to do it ?
Thanks for your time
ankur
peace

Replies are listed 'Best First'.
Re: interchanging variables the tough way
by lhoward (Vicar) on Aug 30, 2000 at 18:57 UTC
    I've always found the "obfuscated xor" technique fun. I avoid it in "real" code 'cause it is very unreadable. But it can be fun to amaze and bewilder your friends and co-workers.
    $a=$a ^ $b; $b=$a ^ $b; $a=$a ^ $b; $b=$b ^ $c; $c=$b ^ $c; $b=$b ^ $c;
    I end up swapping $a with $b, then $b with $c, (2 2-way swaps) not very original. I'm pretty sure that I (or someone else) could come up with a shorter piece of code that does the same thing, by taking advantage of the fact that it is a 3-way swap.

    One cool thing is because of how perl implements xor on scalars is that this works for "strings" in addition to working for "numbers". Still won't work for refrences or undef though :(.

    UPDATE

    I've done some experiments and managed to find 90 diffrent ways of doing the xor 3-way swap with 6 xor operations. I wasn't able to find any with fewer than 6 xor operations. The only "advantage" is that some of the new ones I discovered are harder to read/follow. Example:

    $b=$b^$c; $a=$a^$c; $c=$a^$c; $a=$a^$b; $a=$a^$c; $b=$a^$b;
RE: interchanging variables the tough way
by eduardo (Curate) on Aug 30, 2000 at 18:18 UTC
    In this node, we discussed the benchmarking and all of the wonderful reasons why even in perl, it is faster to use a temporary variable. Enjoy!
Re: interchanging variables the tough way (BENCHMARK)
by eduardo (Curate) on Aug 30, 2000 at 19:56 UTC
    Ok, so as an update in benchmarking, I have written a quick benchmark of the 3 main swapping mechanisms I see written here. I also wanted to say, that the xor technique really just blew me away... never seen that before... anyways, here are some benchmarks:
    #!/usr/bin/perl -w use vars qw/$a $b/; use strict; use Benchmark; $a = 0; $b = 1; sub traditional { my $c = $a; $a = $b; $b = $c; } sub arrays { ($b, $a) = ($a, $b); } sub xor { $a = $a ^ $b; $b = $a ^ $b; $a = $a ^ $b; } timethese(-5, { trad => \&traditional, arrays => \&arrays, xor => \&xor, }); [ed@ci478467-a ed]$ perl swap.pl Benchmark: running arrays, trad, xor, each for at least 5 CPU seconds. +.. arrays: 5 wallclock secs ( 5.12 usr + 0.00 sys = 5.12 CPU) @ 24 +242.19/s (n=124120) trad: 6 wallclock secs ( 5.00 usr + 0.01 sys = 5.01 CPU) @ 40 +630.74/s (n=203560) xor: 5 wallclock secs ( 5.11 usr + 0.00 sys = 5.11 CPU) @ 30 +829.16/s (n=157537)
    so unfortunatelly, as amazingly cool as the XOR swap is, it seem slike it is slighly slower in perl than just using 3 variables... curses :)
      In ARM assembler the XOR method is actually equal in speed to the temporary variable method, and saves a register, and hence is a standard trick in any ARM programmers macro library. Eg :-
      ; Swap r0,r1 using r2 as temporary ; Takes 3 cycles traditional MOV r2, r0 ; r2 <- r0 MOV r0, r1 ; r0 <- r1 MOV r1, r2 ; r1 <- r2 ; Swap r0, r1 with no extra register ; Takes 3 cycles xor EOR r0, r0, r1 ; r0 <- r0 ^ r1 EOR r1, r0, r1 ; r1 <- r0 ^ r1 EOR r0, r0, r1 ; r0 <- r0 ^ r1
      Apologies for non perl post, but perspective can be useful sometimes ;-)

      Note that XOR will not work properly in at least three cases: if the variable is a large value, a reference, or is undefined. The other two methods handle all three without a problem. (although -w will complain about the undef). Just something to watch out for.

      sub xor { $a = $a ^ $b; $b = $a ^ $b; $a = $a ^ $b; } @foo= qw(bar none at all); $a=999999999999; $b=8888888; print "BEFORE: A=$a B=$b\n"; &xor(); print "AFTER: A=$a B=$b\n\n"; $a=\@foo; $b=8888888; print "BEFORE: A=$a ($a->[0]) B=$b\n"; &xor(); print "AFTER: A=$a ($a->[0]) B=$b\n\n"; $a=undef; $b=8888888; print "BEFORE: A=$a B=$b\n"; &xor(); print "AFTER: A=$a B=$b\n\n"; ## Running the above produces: BEFORE: A=999999999999 B=8888888 AFTER: A=8888888 B=4294967295 BEFORE: A=ARRAY(0x87650a4) (bar) B=8888888 AFTER: A=8888888 () B=141971620 BEFORE: A= B=8888888 AFTER: A=8888888 B=0
        You can get the xor method to work on large numeric values by changing the swap function to stringify the values first:
        sub xor { $a = "$a" ^ "$b"; $b = "$a" ^ "$b"; $a = "$a" ^ "$b"; } $a=999999999999; $b=8888888; print "BEFORE: A=$a B=$b\n"; &xor(); print "AFTER: A=$a B=$b\n\n";
        output
        
        BEFORE: A=999999999999 B=8888888
        AFTER:  A=8888888 B=999999999999
        
Re: interchanging variables the tough way
by turnstep (Parson) on Aug 30, 2000 at 18:09 UTC

    The canonical answer (that perl is able to sometimes avoid) is to use a temporary variable, like so:

    my $temp; $temp=$a; $a=$b; $b=$c; $c=$temp;

    In the above code, we save the value of $a to a temporary variable. Then we assign $a to $b - without worrying about clobbering the value of $a, because we have just saved it. And now that the value in $b has been copied to $a, we can safely assign into $b, etc. etc...

    Update: Thanks to those below. Yes, it should assign $c back to $temp, not $a.

      Um, you are supposed to actually assign $temp to $c at the end...

      I am a big fan of Perl constructs that result in my making fewer mistakes. Particularly silly thinkos like the above (which I am likewise prone to). :-)

      Should the code snippet not read
      my $temp; $temp=$a; $a=$b; $b=$c; $c=$temp;
      Or am I missing something (-;
      Sorry
      I completely forgot to type without using a fourth variable
      .Can we do it without using a fourth variable?
Re: interchanging variables the tough way
by vaevictus (Pilgrim) on Aug 30, 2000 at 18:57 UTC
    oR! Try this:
    my @temp=(1,2,3); print "$temp[0] .. $temp[1] .. $temp[2]\n"; push(@temp,shift(@temp)); ($a,$b,$c)=@temp; print "$a .. $b .. $c \n";
Re: interchanging variables the tough way
by gnat (Beadle) on Aug 30, 2000 at 21:55 UTC
    sub invert { return @_ unless @_>1; ($_[0],$_[-1]) = ($_[-1],$_[0]); invert(@_[1..$#_-1]); } @l = (10..20); print "@l becomes "; invert @l; print "@l\n";
RE: interchanging variables the tough way
by t0mas (Priest) on Aug 31, 2000 at 12:45 UTC
    How about an unnamed sub (in the spirit of TMTOWTDI):
    $a=1,$b=2,$c=3,$d=4,$e=5; ($a,$b,$c,$d,$e) = sub {push@_,shift;return@_}->($a,$b,$c,$d,$e); print "\$a=$a,\$b=$b,\$c=$c,\$d=$d,\$e=$e\n";
    It will probably perform worse than the other suggestions though...

    /brother t0mas

      ++ for amusement value, nice one :-)

      With a sub you can actually stop having to retype the list two times. Which is a nice feature:
      $a=1,$b=2,$c=3,$d=4,$e=5; &rotate($a, $b, $c, $d, $e); print "\$a=$a,\$b=$b,\$c=$c,\$d=$d,\$e=$e\n"; sub rotate { my $temp = $_[0]; foreach (0..($#_ - 1)) { $_[$_] = $_[$_+1]; } $_[-1] = $temp; }
      OK, this is definitely slower, but you see the point.
        Looking at this bugs me.

        Manipulating your input arguments this way works, but the side-effects can surprise and amaze. I would not do this lightly.

        My overall feeling is that if you feel the need to rotate your data, you should have an array rather than a list of variables. Then you could

        push @array, shift @array;
        much faster, without resorting to nasty side-effects.

        Generally when I see myself wanting to resort to "bad" techniques like side-effects, I take that as a danger sign and go looking for a more fundamental mistake in my code...

What about...
by gaspodethewonderdog (Monk) on Aug 30, 2000 at 19:23 UTC
    Alright, this is definitely silly, and nobody would do this but... you just wanted some ideas
    $s = "a=1, b=2, c=3"; $s =~ s/a=(\d+), b=(\d+), c=(\d+)/a=$2, b=$3, c=$1/;
    and then there is this, which uses the hash the same way as the array example you gave... but it's with a hash so it's different hehe :)
    @n{a, b, c} = (1, 2, 3); @n{a, b, c} = @n{b, c, a};
    hrmm... I had another example... but I'll quit while I'm ahead and don't have people questioning my sanity.
Re: interchanging variables the tough way
by kilinrax (Deacon) on Aug 30, 2000 at 19:28 UTC
    Yeah, delving back into my old days with assembler, you can do it using xor:
    $a = $a ^ $b; $b = $a ^ $b; $a = $a ^ $b;
    This swaps 2 variables without using a temporary, simply do it twice to swap three, e.g:
    $a = $a ^ $b; $b = $a ^ $b; $a = $a ^ $b; $b = $b ^ $c; $c = $b ^ $c; $b = $b ^ $c;
    Hope this helps ;-)
RE: interchanging variables the tough way
by chromatic (Archbishop) on Aug 31, 2000 at 08:23 UTC
    I usually forget about list subscripting, but it's an option:

    ($a, $b, $c) = ($a, $b, $c)[1, 2, 0];

    Ugly, eh?

RE: interchanging variables the tough way
by wepu (Initiate) on Aug 31, 2000 at 03:27 UTC
    The problem with the XOR is that it produces unpredictable results when used on overlapping peices of memory. This is a much bigger problem in C, and I am pretty sure it would never happen in perl.
      sub xorswap { $_[0] ^= $_[1] ^= $_[0] ^= $_[1]; } $str="XORing is FUN!"; print "($str)\n"; xorswap( substr($str,0,6), substr($str,-6) ); print "($str)\n"; xorswap( substr($str,0,6), substr($str,-6) ); print "($str)\n"; xorswap( substr($str,0,9), substr($str,-9) ); print "($str)\n"; xorswap( $str, $str ); print "($str)\n"; __END__ (XORing is FUN!) (s FUN! iXORing) (XORing is FUN!) (!u'R y:u;ng is) ( )

      Note that the last case is the real reason I think you shouldn't do XOR swapping. After all, it would break Adam's Fisher-Yates Shuffle improvement. ;->

      P.S. I really only posted because I hadn't seen anyone code xorswap the "proper" way. (:

              - tye (but my friends call me "Tye")
        Well yes Tye, you can't XOR something with it-self and expect anything other then 0. That would be illogical. But it is a good point about the Fisher-Yates Shuffle, if you are using XOR to swap to locations you must be careful that the locations aren't the same... which is probably why the original algorithm does the test.
Re: interchanging variables the tough way
by ANKUR (Novice) on Aug 30, 2000 at 18:48 UTC
    Replying to myself here
    oops i completely forgot to type without using any fourth
    variable. Sorry. Can we do it without using a fourth
    variable.
    Thanks
    Ankur

      The short answer? No.

      The complete answer? "It depends" - on what you have stored inside those variables - numbers, strings, references, undefined?