Skip to content

Commit a7b5f37

Browse files
committed
shuffle
1 parent d32eabe commit a7b5f37

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
The simple solution could be:
2+
3+
```js run
4+
*!*
5+
function shuffle(array) {
6+
array.sort(() => Math.random() - 0.5);
7+
}
8+
*/!*
9+
10+
let arr = [1, 2, 3];
11+
shuffle(arr);
12+
alert(arr);
13+
```
14+
15+
That somewhat works, because `Math.random()-0.5` is a random number that may be positive or negative, so the sorting function reorders elements randomly.
16+
17+
But because the sorting function is not meant to be used this way, not all permutations have the same probability.
18+
19+
For instance, consider the code below. It runs `shuffle` 1000000 times and counts appearances of all possible results:
20+
21+
```js run
22+
function shuffle(array) {
23+
array.sort(() => Math.random() - 0.5);
24+
}
25+
26+
// counts of appearances for all possible permutations
27+
let count = {
28+
'123': 0,
29+
'132': 0,
30+
'213': 0,
31+
'231': 0,
32+
'321': 0,
33+
'312': 0
34+
};
35+
36+
for(let i = 0; i < 1000000; i++) {
37+
let array = [1, 2, 3];
38+
shuffle(array);
39+
count[array.join('')]++;
40+
}
41+
42+
// show counts of all possible permutations
43+
for(let key in count) {
44+
alert(`${key}: ${count[key]}`);
45+
}
46+
```
47+
48+
An example result (for V8, July 2017):
49+
50+
```js
51+
123: 250706
52+
132: 124425
53+
213: 249618
54+
231: 124880
55+
312: 125148
56+
321: 125223
57+
```
58+
59+
We can see the bias clearly: `123` and `213` appear much more often than others.
60+
61+
The result of the code may vary between JavaScript engines, but we can already see that the approach is unreliable.
62+
63+
Why it doesn't work? Generally speaking, `sort` is a "black box": we throw an array and a comparison function into it and expect the array to be sorted. But due to the utter randomness of the comparison the black box goes mad, and how exactly it goes mad depends on the concrete implementation that differs between engines.
64+
65+
There are other good ways to do the task. For instance, there's a great algorithm called [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle). The idea is to walk the array in the reverse order and swap each element with a random one before it:
66+
67+
```js
68+
function shuffle(array) {
69+
for(let i = array.length - 1; i > 0; i--) {
70+
let j = Math.floor(Math.random() * (i+1)); // random index from 0 to i
71+
[array[i], array[j]] = [array[j], array[i]]; // swap elements
72+
}
73+
}
74+
```
75+
76+
Let's test it the same way:
77+
78+
```js run
79+
function shuffle(array) {
80+
for(let i = array.length - 1; i > 0; i--) {
81+
let j = Math.floor(Math.random() * (i+1));
82+
[array[i], array[j]] = [array[j], array[i]];
83+
}
84+
}
85+
86+
// counts of appearances for all possible permutations
87+
let count = {
88+
'123': 0,
89+
'132': 0,
90+
'213': 0,
91+
'231': 0,
92+
'321': 0,
93+
'312': 0
94+
};
95+
96+
for(let i = 0; i < 1000000; i++) {
97+
let array = [1, 2, 3];
98+
shuffle(array);
99+
count[array.join('')]++;
100+
}
101+
102+
// show counts of all possible permutations
103+
for(let key in count) {
104+
alert(`${key}: ${count[key]}`);
105+
}
106+
```
107+
108+
The example output:
109+
110+
```js
111+
123: 166693
112+
132: 166647
113+
213: 166628
114+
231: 167517
115+
312: 166199
116+
321: 166316
117+
```
118+
119+
Looks good now: all permutations appear with the same probability.
120+
121+
Also, performance-wise the Fisher-Yates algorithm is much better, there's no "sorting" overhead.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
importance: 3
2+
3+
---
4+
5+
# Shuffle an array
6+
7+
Write the function `shuffle(array)` that shuffles (randomly reorders) elements of the array.
8+
9+
Multiple runs of `shuffle` may lead to different orders of elements. For instance:
10+
11+
```js
12+
let arr = [1, 2, 3];
13+
14+
shuffle(arr);
15+
// arr = [3, 2, 1]
16+
17+
shuffle(arr);
18+
// arr = [2, 1, 3]
19+
20+
shuffle(arr);
21+
// arr = [3, 1, 2]
22+
// ...
23+
```
24+
25+
All element orders should have an equal probability. For instance, `[1,2,3]` can be reordered as `[1,2,3]` or `[1,3,2]` or `[3,1,2]` etc, with equal probability of each case.

0 commit comments

Comments
 (0)