Browse files

Merge pull request #72 from d8uv/patch-1

Change the shuffle to the Fisher-Yates shuffle
  • Loading branch information...
2 parents 49370e6 + fa13f3b commit d0ee5cbcb78edf4c3a6ae806f1a74b3537ff9390 @dbrady dbrady committed Feb 28, 2013
Showing with 82 additions and 5 deletions.
  1. +82 −5 chapters/arrays/
@@ -9,17 +9,94 @@ You want to shuffle the elements in an array.
## Solution
-The JavaScript Array `sort()` method accepts a custom sort function. We can write a `shuffle()` method to add some convenience.
+The [Fisher-Yates shuffle] is a highly efficient and completely unbiased way to randomize
+the elements in an array. It's a fairly simple method: Start at the end of the list, and
+swap the last element with a random element from earlier in the list. Go down one and
+repeat, until you're at the beginning of the list, with all of the shuffled elements
+at the end of the list. This [Fisher-Yates shuffle Visualization] may help you understand
+the algorithm.
{% highlight coffeescript %}
-Array::shuffle = -> @sort -> 0.5 - Math.random()
+shuffle = (a) ->
+ # From the end of the list to the beginning, pick element `i`.
+ for i in [a.length-1..1]
+ # Choose random element `j` to the front of `i` to swap with.
+ j = Math.floor Math.random() * (i + 1)
+ # Swap `j` with `i`, using destructured assignment
+ [a[i], a[j]] = [a[j], a[i]]
+ # Return the shuffled array.
+ a
# => [ 3, 1, 5, 6, 4, 8, 2, 9, 7 ]
{% endhighlight %}
+[Fisher-Yates shuffle]:
+[Fisher-Yates Shuffle Visualization]:
## Discussion
-For more background on how this shuffle logic works, see this [discussion at StackOverflow](
+### The Wrong Way to do it
+There is a common--but terribly wrong way--to shuffle an array, by sorting by a random
+{% highlight coffeescript %}
+shuffle = (a) -> a.sort -> 0.5 - Math.random()
+{% endhighlight %}
+If you do a sort randomly, it should give you a random order, right? Even [Microsoft used
+this random-sort algorithm][msftshuffle]. Turns out, [this random-sort algorithm produces
+biased results][naive], because it only has the illusion of shuffling. Randomly sorting
+will not result in a neat, tidy shuffle; it will result in a wild mass of inconsistent
+### Optimizing for speed and space
+The solution above isn't as fast, or as lean, as it can be. The list comprehension, when
+transformed into Javascript, is far more complex than it needs to be, and the
+destructured assignment is far slower than dealing with bare variables. The following
+code is less idiomatic, and takes up more source-code space... but will compile down
+smaller and run a bit faster:
+{% highlight coffeescript %}
+shuffle = (a) ->
+ i = a.length
+ while --i > 0
+ j = ~~(Math.random() * (i + 1)) # ~~ is a common optimization for Math.floor
+ t = a[j]
+ a[j] = a[i]
+ a[i] = t
+ a
+{% endhighlight %}
+### Extending Javascript to include this shuffle.
+The following code adds the shuffle function to the Array prototype, which means that
+you are able to run it on any array you wish, in a much more direct manner.
+{% highlight coffeescript %}
+Array::shuffle = ->
+ for i in [@length-1..1]
+ j = Math.floor Math.random() * (i + 1)
+ [@[i], @[j]] = [@[j], @[i]]
+ @
+# => [ 3, 1, 5, 6, 4, 8, 2, 9, 7 ]
+{% endhighlight %}
+**Note:** Although it's quite common in languages like Ruby, extending native objects is
+often considered bad practice in JavaScript (see: [Maintainable JavaScript: Don’t modify
+objects you don’t own][dontown]; [Extending built-in native objects. Evil or not?]
+Also, if you think you'll be using a lot of these utility functions, consider using a
+utility library, like [Lo-dash]( They include a lot of nifty
+features, like maps and forEach, in a cross-browser, lean, high-performance way.
-**Note:** Although it's quite common in languages like Ruby, extending native objects is often considered bad practice in JavaScript (see: [Maintainable JavaScript: Don’t modify objects you don’t own](; [Extending built-in native objects. Evil or not?](

0 comments on commit d0ee5cb

Please sign in to comment.