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

The cookbook (and this site) provide a small algorithm for shuffling array elements in place. It contains two constructs I have not seen and can't place, so I seek help in understanding these:

The code looks like this:

for ($i = @$array; --$i; ) { my $j = int rand ($i+1); next if $i == $j; @$array[$i,$j] = @$array[$j,$i] }
My 2 questions:

1) in the for loop: I see a predecrement where a guard should be. what's with that? I understand C-style for loops to be
for(init..; guard..; increment/decrement) {..}

2) $array is a reference to a 1-D array, yet the last line here uses indices appropriate for a 2-D array. Why does this work and how?

Nuggets of wisdom would be dearly appreciated.

Replies are listed 'Best First'.
Re: help in understanding the standard array shuffle
by ChOas (Curate) on Feb 14, 2001 at 17:56 UTC
    Hi!

    1: this works like a C-style for: The 2nd argument is the 'while' (or
    as you call it 'guard') part... when $i reaches '0' it is no longer true,
    the for loop breaks

    2: It is a slice on the 1-D array, it means : take the elements at
    position $j, and $i, and put them in position $i and $j

    Hope this helps a little bit

    GreetZ!,
      ChOas

    print "profeth still\n" if /bird|devil/;
Re: help in understanding the standard array shuffle
by kschwab (Vicar) on Feb 14, 2001 at 18:03 UTC
    Q1) In this case:
    • $i is initially assigned the length of the list (a list in scalar context returns the length).
    • the loop runs while --$i (the "guard") is true. Basically it runs through the array backwards.
    Q2)
    • It looks a bit like a 2-D array, but it's actually an array slice. It's swapping the contents of two array elements. A 2-D array would have a $ rather than a @ at the front.
Re: help in understanding the standard array shuffle
by Gloom (Monk) on Feb 14, 2001 at 18:35 UTC
    I dont know if it's efficient, but it works =)

    # indexed array @array = sort { (-1,1)[int rand 2] } @array; # or trinary alternative @array = sort { int rand 2 ? 1 : -1 } @array;

    Hope this helps

    Update

    In fact, it's really a bad idea to use this for randomize an array...

    See the result of this script :
    #!/usr/bin/perl -w use strict; my @array = qw( peach banana mango orange apple cherry ); my %result; $, = "\t\t"; $\ = "\n"; for(1..10000) { @_ = sort { (-1,1)[int rand 2] } @array; for(0..$#array) { $result{ $_[$_] , $_ }++ } } print '' , (0..$#array); for my $fruit ( @array ) { print $fruit , map { $result{ $fruit , $_ } } (0..$#array); }
    Return :
    0 1 2 3 4 5 peach 2982 3026 2010 1095 573 314 banana 2961 2973 1960 1172 602 332 mango 1996 2004 2333 1866 1100 701 orange 1156 1116 1914 2558 1999 1257 apple 602 561 1162 2030 3171 2474 cherry 303 320 621 1279 2555 4922
    Not really randomized huh ? ...
      The sort order is required to be 'consistent': that is, if A > B and B > C, then when asked about A and C, you'd better durn well return A > C. The underlying qsort(3) function demands it, and was optimized for it.

      Because your randomizing function has no memory to guarantee this consistency, you'll be very surprised by the behavior. Older qsort functions would in fact dump core or lose data on this kind of pseudo-shuffle. Modern perls use an internal sort function that at least doesn't lose the data, but it's really not the way to do a shuffle nicely. See the FAQ solution instead, quoted at the head of this thread.

      -- Randal L. Schwartz, Perl hacker

      See update...ugh A nit perhaps, but...:

      It would seem this shuffle guarantees that no element remains in it's current position. This seems less than random.

      @array= sort { (-1,0,1)[int rand 3]} @array
      I suspect you never see this type of thing because of the inefficiency. The cookbook example is making length(@foo) or less comparisons, the random sort is potentially doing more than length(@foo) comparisons.

      Update: Hmm...my first statement, after further thought, appears to be silly.

        You are incorrect, the routine leaves open the possibility of keeping an item in place. Note the line next if ... there. In fact, as I recall it, that shuffle can be shown to have an even chance of producing any permutation with a true random source and is pretty damn good even with a really BAD random source.

        Also, the rand trick you employ can exit early and leave chunks untouched. sortrather relies on consistency, if b>a and c>b then c>a should be implied. If I read the sort code right, violating that could lead to serious goofs like potentially never ending and such.

        --
        $you = new YOU;
        honk() if $you->love(perl)

        It would seem this shuffle guarantees that no element remains in it's current position. This seems less than random.

        My tests show that SOME elements stay in place !
        Did I miss something ?

        Note: I suspect however that the distribution is not perfect(the items "don't move too far from their position" so i suspect the "distribution" to be biased)...

        Anyway it's ok for me the way it is, just have to remember it's not a true 'statistical random' change...
Re: help in understanding the standard array shuffle
by TheoPetersen (Priest) on Feb 14, 2001 at 20:17 UTC
    To amplify the points made by ChOas and kschwab:
    the loop test of --$i also means that the loop won't execute if the array has a single element, since the test is made before the first execution. It also means that the loop will die with mysterious errors if the array is empty, since -1 passes the test.