Array shuffling gotchas

Shuffling an array is a fairly common task in the world of maxscript, and even if you don't do it quite right it's still okay for most of the tasks it's used for. However when unique array elements get dropped in the process and other are repeated, it's definitely not the expected outcome.

I tend not to overuse local variables when they are only used once and it doesn't hinder readability (why, yes, I do fancy me some functional programming from time to time). In this context, though, it fails miserably:

fn shuffle arr =
    for counter = arr.count to 1 by -1 collect
        swap arr[random 1 counter] arr[counter]

Supposedly, this would shuffle the passed array and return a reversed copy of the shuffled array (because swap returns the first value that was passed to it – which is the value we use for replacing the original one here – and we traverse the array in reverse order). The actual output of this function is quite different:

shuffle (#{1..20} as array)
-- #(9, 5, 6, 12, 16, 3, 13, 19, 14, 6, 5, 5, 19, 6, 5, 3, 5, 19, 5, 5)

Note that this is not a proper variant of Fisher–Yates shuffle as the item can be swapped with itself, yet it's usually good enough and this implementation detail has nothing to do with all those repeated and dropped values it outputs.

Now let's see what happens when we introduce a variable to hold the result of random call instead:

fn shuffle arr =
    for counter = arr.count to 1 by -1 collect
        local swapIndex = random 1 counter
        swap arr[swapIndex] arr[counter]
shuffle (#{1..20} as array)
-- #(14, 7, 4, 16, 8, 6, 20, 15, 2, 3, 9, 13, 19, 5, 17, 12, 10, 1, 11, 18)

I got bitten by it more than once and although I've tackled it before (randomizeArray being one of the examples), I tend to forget that and go WTF when I meet the swap enigma again, the best evidence being this shuffle function with manual swap using temporary variable.

Looking at that unexplained, uncommented piece of code again, I should probably explain what I did there as well, this blog has been quiet for a long time and I guess it's about time to change that.

DISCLAIMER: All scripts and snippets are provided as is under Creative Commons Zero (public domain, no restrictions) license. The author and this blog cannot be held liable for any loss caused as a result of inaccuracy or error within these web pages. Use at your own risk.

This Post needs Your Comment!

Return to top