Skip to content

Commit fa13f3b

Browse files
committed
Change the shuffle to the Fisher-Yates shuffle
The current "Shuffling Array Elements" page recommends a naive algorithm, which produces  a slow and biased shuffle. Included is a highly idiomatic refactorization of the Fisher- Yates shuffle (aka the Knuth Shuffle), a less-idiomatic-but-better refactorization, and a version of the algorithm that adds to Array.prototype, for those that program in that  style.
1 parent 49370e6 commit fa13f3b

File tree

1 file changed

+82
-5
lines changed

1 file changed

+82
-5
lines changed

chapters/arrays/shuffling-array-elements.md

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,94 @@ You want to shuffle the elements in an array.
99

1010
## Solution
1111

12-
The JavaScript Array `sort()` method accepts a custom sort function. We can write a `shuffle()` method to add some convenience.
12+
The [Fisher-Yates shuffle] is a highly efficient and completely unbiased way to randomize
13+
the elements in an array. It's a fairly simple method: Start at the end of the list, and
14+
swap the last element with a random element from earlier in the list. Go down one and
15+
repeat, until you're at the beginning of the list, with all of the shuffled elements
16+
at the end of the list. This [Fisher-Yates shuffle Visualization] may help you understand
17+
the algorithm.
1318

1419
{% highlight coffeescript %}
15-
Array::shuffle = -> @sort -> 0.5 - Math.random()
20+
shuffle = (a) ->
21+
# From the end of the list to the beginning, pick element `i`.
22+
for i in [a.length-1..1]
23+
# Choose random element `j` to the front of `i` to swap with.
24+
j = Math.floor Math.random() * (i + 1)
25+
# Swap `j` with `i`, using destructured assignment
26+
[a[i], a[j]] = [a[j], a[i]]
27+
# Return the shuffled array.
28+
a
1629

17-
[1..9].shuffle()
30+
shuffle([1..9])
1831
# => [ 3, 1, 5, 6, 4, 8, 2, 9, 7 ]
1932
{% endhighlight %}
2033

34+
[Fisher-Yates shuffle]: http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
35+
[Fisher-Yates Shuffle Visualization]: http://bost.ocks.org/mike/shuffle/
36+
2137
## Discussion
2238

23-
For more background on how this shuffle logic works, see this [discussion at StackOverflow](http://stackoverflow.com/questions/962802/is-it-correct-to-use-javascript-array-sort-method-for-shuffling).
39+
### The Wrong Way to do it
40+
41+
There is a common--but terribly wrong way--to shuffle an array, by sorting by a random
42+
number.
43+
44+
{% highlight coffeescript %}
45+
shuffle = (a) -> a.sort -> 0.5 - Math.random()
46+
{% endhighlight %}
47+
48+
If you do a sort randomly, it should give you a random order, right? Even [Microsoft used
49+
this random-sort algorithm][msftshuffle]. Turns out, [this random-sort algorithm produces
50+
biased results][naive], because it only has the illusion of shuffling. Randomly sorting
51+
will not result in a neat, tidy shuffle; it will result in a wild mass of inconsistent
52+
sorting.
53+
54+
[msftshuffle]: http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
55+
[naive]: http://www.codinghorror.com/blog/2007/12/the-danger-of-naivete.html
56+
57+
### Optimizing for speed and space
58+
59+
The solution above isn't as fast, or as lean, as it can be. The list comprehension, when
60+
transformed into Javascript, is far more complex than it needs to be, and the
61+
destructured assignment is far slower than dealing with bare variables. The following
62+
code is less idiomatic, and takes up more source-code space... but will compile down
63+
smaller and run a bit faster:
64+
65+
{% highlight coffeescript %}
66+
shuffle = (a) ->
67+
i = a.length
68+
while --i > 0
69+
j = ~~(Math.random() * (i + 1)) # ~~ is a common optimization for Math.floor
70+
t = a[j]
71+
a[j] = a[i]
72+
a[i] = t
73+
a
74+
{% endhighlight %}
75+
76+
### Extending Javascript to include this shuffle.
77+
78+
The following code adds the shuffle function to the Array prototype, which means that
79+
you are able to run it on any array you wish, in a much more direct manner.
80+
81+
{% highlight coffeescript %}
82+
Array::shuffle = ->
83+
for i in [@length-1..1]
84+
j = Math.floor Math.random() * (i + 1)
85+
[@[i], @[j]] = [@[j], @[i]]
86+
@
87+
88+
[1..9].shuffle()
89+
# => [ 3, 1, 5, 6, 4, 8, 2, 9, 7 ]
90+
{% endhighlight %}
91+
92+
**Note:** Although it's quite common in languages like Ruby, extending native objects is
93+
often considered bad practice in JavaScript (see: [Maintainable JavaScript: Don’t modify
94+
objects you don’t own][dontown]; [Extending built-in native objects. Evil or not?]
95+
[extendevil]).
96+
97+
Also, if you think you'll be using a lot of these utility functions, consider using a
98+
utility library, like [Lo-dash](http://lodash.com/). They include a lot of nifty
99+
features, like maps and forEach, in a cross-browser, lean, high-performance way.
24100

25-
**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](http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/); [Extending built-in native objects. Evil or not?](http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/)).
101+
[dontown]: http://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/
102+
[extendevil]: http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/

0 commit comments

Comments
 (0)