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

A better title for the node would be: "...and then I realized that it wasn't so great."

I was reading over the various level powers, when I came across the "Categorized Questions and Answers". Though I haven't perused this section yet, I thought that I would do so, then add a deck-shuffling routine to it that I wrote a few days ago. The routine was magic to me, but simple enough that other newbs should be able to understand it.

But before I investigated further as to whether or not a question/answer like this had been posted, I decided to take a second look at my code.

I've tested it a few times, and it works just fine, but HOW it works is absolutely baffling to me, now (and I'm curious if it will continue to work or if it will get patched out of compiling in future versions of the language).

The code:

sub shuffle_Deck { my @deck; for (0 .. $#_) { my $rand = int(rand(@_)); push(@deck, splice(@_,$rand,1)); } return @deck; }

I would have expected the $_ in the for loop to take on the values between 0 and the passed arr's $# (as described in the for (LIST)). However, it appears to retain the value of the scalar within the passed array, rather than just a integer value...

This happens to be what I wanted anyways. :)

Is this an example of perl taking a Pretty Good Guess, or is the language actually supposed to do this?

Update: My above code was originally ...int(rand($#_))..., which left the bottom card at the bottom of the deck forever. :)

$scratchpad_public = 0 unless $scratchpad;

Replies are listed 'Best First'.
Re: Unexpected value of $_ in a for loop.
by Roger (Parson) on Jan 16, 2004 at 05:07 UTC
    $_ is not used in the loop. @_ and $#_ (last index of @_) actually refer to the parameters passed into the sub.

    Here's a more efficient shuffling algorithm, especially on large arrays:
    use strict; use warnings; use Data::Dumper; my @cards = qw/ A 2 3 4 5 6 7 8 9 10 J Q K /; @cards = shuffle_Deck(@cards); print Dumper(\@cards); sub shuffle_Deck { my @new = (); for (@_) { my $i = int rand($#new+1); push @new, $new[$i]; $new[$i] = $_; }; return(@new); }
Re: Unexpected value of $_ in a for loop.
by Coruscate (Sexton) on Jan 16, 2004 at 05:21 UTC

    If you don't quite understand Roger's explanation, here is a rewrite of the code that is much more obvious:

    sub shuffle_deck { my @orig_deck = @_; my @new_deck; for (0 .. $#orig_deck) { my $rand = int(rand $#orig_deck); push @new_deck, splice(@orig_deck, $rand, 1); } return @new_deck; }

    I probably would have used a fisher-yates shuffle or the shuffle() from List::Util.

      I probably would have used a fisher-yates shuffle or the shuffle() from List::Util.
      Benchmarking shows that the XS version is much, much faster than anything done in Perl. But if you stick to a Perl solution, one that acts on an array instead of a list is the winner. Here are some benchmark results, followed by the benchmark itself. Note the dramatic decrease in performance of 'coruscate' when the lenght of the list to be sorted increases - this is due to the splicing, which results in a quadratic algorithm; all other solutions are linear.

      Benchmarking with a deck of 13. Rate roger coruscate fisher fisher2 list roger 18931/s -- -0% -4% -21% -85% coruscate 18959/s 0% -- -4% -20% -85% fisher 19747/s 4% 4% -- -17% -85% fisher2 23821/s 26% 26% 21% -- -82% list 129419/s 584% 583% 555% 443% -- Benchmarking with a deck of 52. Rate fisher roger coruscate fisher2 list fisher 4917/s -- -2% -3% -18% -85% roger 5021/s 2% -- -1% -17% -85% coruscate 5062/s 3% 1% -- -16% -85% fisher2 6031/s 23% 20% 19% -- -82% list 33210/s 575% 561% 556% 451% -- Benchmarking with a deck of 1024. Rate coruscate fisher roger fisher2 list coruscate 246/s -- -2% -6% -21% -86% fisher 251/s 2% -- -4% -19% -86% roger 261/s 6% 4% -- -16% -85% fisher2 310/s 26% 23% 19% -- -82% list 1767/s 619% 603% 577% 471% -- Benchmarking with a deck of 50000. Rate coruscate fisher roger fisher2 list coruscate 0.909/s -- -72% -73% -85% -94% fisher 3.19/s 251% -- -7% -47% -78% roger 3.43/s 277% 7% -- -43% -76% fisher2 6.03/s 563% 89% 76% -- -58% list 14.3/s 1474% 349% 318% 137% -- #!/usr/bin/perl use strict; use warnings; use List::Util; use Benchmark qw /timethese cmpthese/; our (@cards, @cards2, @tmp); foreach my $size (qw /13 52 1024 50000/) { @cards = 1 .. $size; @cards2 = 1 .. $size; print "Benchmarking with a deck of $size.\n"; cmpthese -10 => { roger => '@tmp = roger @cards', coruscate => '@tmp = coruscate @cards', fisher => '@tmp = fisher @cards', fisher2 => ' fisher2 \@cards2', list => '@tmp = List::Util::shuffle @cards', }; } sub roger { my @new = (); for (@_) { my $i = int rand($#new+1); push @new, $new[$i]; $new[$i] = $_; }; return(@new); } sub coruscate { my @orig_deck = @_; my @new_deck; for (0 .. $#orig_deck) { my $rand = int(rand $#orig_deck); push @new_deck, splice(@orig_deck, $rand, 1); } return @new_deck; } sub fisher { my $i = my @deck = @_; while (-- $i) { my $j = int rand ($i + 1); @deck [$i, $j] = @deck [$j, $i]; } @deck; } sub fisher2 { my $deck = shift; my $i = @$deck; while (-- $i) { my $j = int rand ($i + 1); @$deck [$i, $j] = @$deck [$j, $i]; } } __END__

      Abigail

Re: Unexpected value of $_ in a for loop.
by pg (Canon) on Jan 16, 2004 at 05:23 UTC
    "I would have expected the $_ in the for loop to take on the values between 0 and the passed arr's $#"

    You were right, and this approves it:

    use strict; use warnings; shuffle_Deck(0,2,4,6,8,10,12,14); sub shuffle_Deck { my @deck; for (0 .. $#_) { print "[$_]"; my $rand = int(rand($#_)); push(@deck, splice(@_,$rand,1)); } return @deck; }

    This prints: [0][1][2][3][4][5][6][7]

      (Firstly, thanks to the monks above for more efficient routines.)

      The statement: push(@deck, splice(@_,$rand,1)); is what puzzles me (in my code).

      After writing a sub:

      my @arr = (5 .. 10); call_sub(@arr); sub call_sub { print "\n\n@_\n\n"; for (0 .. 4) { print "$_\t@_\n"; } print "\n\n"; }

      I believe that $_ is a separate variable from @_ (and you can reference a scalar in @_ with $_[$sc]).

      I had always assumed that $_ and @_ were connected, and that @_ referenced the elements in the for list as an array (which it doesn't).

      It's a rather new discovery for me. Looking at how useful this is (rather than having to make temporary arrays in all your subroutines to carry parameters before going into a for loop), I see why it was done that way.

      Dave.

        I had always assumed that $_ and @_ were connected

        No. Well, yes, but not like that. They are connected in the sense that both of them have general punctuation-variable magic that exempts them from certain strictures and stuff. Also they can both be accessed using the typeglob *_ (as can %_ and &_ and the filehandle named _ if there is one). But that's all. Their values are not connected in any way.

        This is a general point that applies to all variables in Perl. Observe:

        no strict refs; use Data::Dumper; for $varname ('foo', 'bar', ':', '@%$!') { ${$varname} = "some scalar value"; @{$varname} = ('list', $$varname, \$$varname); %{$varname} = ( scalar => \$$varname, array => \@$varname ); print "-"x$=, $/; ::($varname); } sub :: { for (0) { print "For the variables named '$_[$_]':\n"; print Dumper(${$_[$_]}, \@{$_[$_]}, \%{$_[$_]}); } }

        Variables named '_' behave the same way, but it cannot be demonstrated with the above code because their values get overwritten in the middle of the demonstration because of the nature of their special magic (except %_, which has no special magic in Perl5).

        You can have a lot of fun writing obfuscations that abuse this, but there are also legitimate reasons to name an array and a scalar the same thing. One good example is if the sole purpose of the scalar is to serve as an index into the array (e.g., keeping track of the element your code is currently looking at). Especially if you have several such arrays each with its own current subscript scalar, naming them after one another can make it easier to keep track of what's what. Some would say that you should call them @foo and $fooindex or somesuch, but I tend to like to avoid superfluous verbosity in variable names.


        $;=sub{$/};@;=map{my($a,$b)=($_,$;);$;=sub{$a.$b->()}} split//,".rekcah lreP rehtona tsuJ";$\=$ ;->();print$/
Re: Unexpected value of $_ in a for loop.
by Albannach (Monsignor) on Jan 16, 2004 at 05:22 UTC
    You would probably be interested in reading from perldoc -q shuffle which explains why this splicing method may not be the best option.

    --
    I'd like to be able to assign to an luser