-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adding details for shuffle function
- Loading branch information
1 parent
4dbaaa9
commit 8c45a61
Showing
1 changed file
with
257 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
# 8. can you shuffle() an array? | ||
|
||
### Problem | ||
|
||
https://bigfrontend.dev/problem/can-you-shuffle-an-array | ||
|
||
# | ||
|
||
### Problem Description | ||
|
||
How would you implement a `shuffle()` ? | ||
|
||
When passed with an array, it should modify the array inline to generate a randomly picked permutation at the same probability. | ||
|
||
for an array like this: | ||
|
||
```js | ||
const arr = [1, 2, 3, 4]; | ||
``` | ||
|
||
there would be possibly 4! = 24 permutations | ||
|
||
``` | ||
[1, 2, 3, 4] | ||
[1, 2, 4, 3] | ||
[1, 3, 2, 4] | ||
[1, 3, 4, 2] | ||
[1, 4, 2, 3] | ||
[1, 4, 3, 2] | ||
[2, 1, 3, 4] | ||
[2, 1, 4, 3] | ||
[2, 3, 1, 4] | ||
[2, 3, 4, 1] | ||
[2, 4, 1, 3] | ||
[2, 4, 3, 1] | ||
[3, 1, 2, 4] | ||
[3, 1, 4, 2] | ||
[3, 2, 1, 4] | ||
[3, 2, 4, 1] | ||
[3, 4, 1, 2] | ||
[3, 4, 2, 1] | ||
[4, 1, 2, 3] | ||
[4, 1, 3, 2] | ||
[4, 2, 1, 3] | ||
[4, 2, 3, 1] | ||
[4, 3, 1, 2] | ||
[4, 3, 2, 1] | ||
``` | ||
|
||
your `shuffle()` should transform the array in one of the above array, at the same 1/24 probability. | ||
|
||
**notes** | ||
|
||
Your `shuffle()` will be called multiple times, to calculate the probability on each possible result, and test again [standard deviation](https://simple.wikipedia.org/wiki/Standard_deviation) | ||
|
||
ref: https://javascript.info/task/shuffle | ||
|
||
# | ||
|
||
### Solution | ||
|
||
```js | ||
/** | ||
* @param {any[]} arr | ||
*/ | ||
function shuffle(array) { | ||
for (let i = array.length - 1; i > 0; i--) { | ||
let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i | ||
|
||
// swap elements array[i] and array[j] | ||
// we use "destructuring assignment" syntax to achieve that | ||
// same can be written as: | ||
// let t = array[i]; array[i] = array[j]; array[j] = t | ||
[array[i], array[j]] = [array[j], array[i]]; | ||
} | ||
} | ||
``` | ||
|
||
# | ||
|
||
### Explanation | ||
Multiple runs of shuffle may lead to different orders of elements. For instance: | ||
|
||
```js | ||
let arr = [1, 2, 3]; | ||
|
||
shuffle(arr); | ||
// arr = [3, 2, 1] | ||
|
||
shuffle(arr); | ||
// arr = [2, 1, 3] | ||
|
||
shuffle(arr); | ||
// arr = [3, 1, 2] | ||
<br /> | ||
|
||
shuffle(arr); | ||
// ... | ||
``` | ||
|
||
```js | ||
function shuffle(array) { | ||
array.sort(() => Math.random() - 0.5); | ||
} | ||
|
||
let arr = [1, 2, 3]; | ||
shuffle(arr); | ||
``` | ||
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. | ||
|
||
But because the sorting function is not meant to be used this way, not all permutations have the same probability. | ||
|
||
For instance, consider the code below. It runs shuffle 1000000 times and counts appearances of all possible results: | ||
```js | ||
function shuffle(array) { | ||
array.sort(() => Math.random() - 0.5); | ||
} | ||
|
||
// counts of appearances for all possible permutations | ||
let count = { | ||
'123': 0, | ||
'132': 0, | ||
'213': 0, | ||
'231': 0, | ||
'321': 0, | ||
'312': 0 | ||
}; | ||
|
||
for (let i = 0; i < 1000000; i++) { | ||
let array = [1, 2, 3]; | ||
shuffle(array); | ||
count[array.join('')]++; | ||
} | ||
|
||
// show counts of all possible permutations | ||
for (let key in count) { | ||
alert(`${key}: ${count[key]}`); | ||
} | ||
``` | ||
An example result (depends on JS engine): | ||
``` | ||
123: 250706 | ||
132: 124425 | ||
213: 249618 | ||
231: 124880 | ||
312: 125148 | ||
321: 125223 | ||
``` | ||
|
||
let's break this down: | ||
|
||
1. **Define the `shuffle` function**: This function takes an array as an argument and sorts it using a comparator function that returns a random number between -0.5 and 0.5. This effectively shuffles the array, but it doesn't generate a truly random shuffle because not all permutations are equally likely. | ||
|
||
2. **Initialize `count` object**: This object will keep track of the number of times each possible permutation of the array [1, 2, 3] appears when the array is shuffled. | ||
|
||
3. **Run the shuffle function multiple times**: The code that follows (which is not shown in the excerpt) would run the `shuffle` function a large number of times (e.g., 1,000,000 times) and increment the count of the resulting permutation each time. | ||
|
||
4. **Analyze the results**: By looking at the counts of each permutation, you can see that some permutations appear more often than others. This demonstrates that the `shuffle` function does not generate a truly random shuffle, because in a truly random shuffle, all permutations would be equally likely. | ||
|
||
The reason for this bias is that the `Array.sort()` method is not designed to be used with a comparator function that returns a random value. The sort order of two elements should be deterministic and based on the values of the elements themselves, not on a random number. When you use a random comparator function, the behavior of `Array.sort()` is not well-defined, and it can lead to biased results. | ||
|
||
|
||
There are other good ways to do the task. For instance, there’s a great algorithm called Fisher-Yates shuffle. The idea is to walk the array in the reverse order and swap each element with a random one before it: | ||
|
||
```js | ||
function shuffle(array) { | ||
for (let i = array.length - 1; i > 0; i--) { | ||
let j = Math.floor(Math.random() * (i + 1)); | ||
[array[i], array[j]] = [array[j], array[i]]; | ||
} | ||
} | ||
|
||
// counts of appearances for all possible permutations | ||
let count = { | ||
'123': 0, | ||
'132': 0, | ||
'213': 0, | ||
'231': 0, | ||
'321': 0, | ||
'312': 0 | ||
}; | ||
|
||
for (let i = 0; i < 1000000; i++) { | ||
let array = [1, 2, 3]; | ||
shuffle(array); | ||
count[array.join('')]++; | ||
} | ||
|
||
// show counts of all possible permutations | ||
for (let key in count) { | ||
alert(`${key}: ${count[key]}`); | ||
} | ||
``` | ||
|
||
The example output: | ||
``` | ||
123: 166693 | ||
132: 166647 | ||
213: 166628 | ||
231: 167517 | ||
312: 166199 | ||
321: 166316 | ||
``` | ||
|
||
Looks good now: all permutations appear with the same probability. | ||
|
||
Also, performance-wise the Fisher-Yates algorithm is much better, there’s no “sorting” overhead. | ||
|
||
|
||
|
||
# | ||
|
||
### Usage | ||
|
||
```js | ||
function generateRandomArray(n) { | ||
const array = Array.from({ length: n }, (_, i) => i); | ||
shuffle(array); | ||
return array; | ||
} | ||
|
||
const array = generateRandomArray(10); | ||
console.log(array); // [5, 7, 2, 4, 8, 6, 1, 9, 3, 0] | ||
console.log(array); // [0, 5, 4, 9, 7, 8, 1, 2, 3, 6] | ||
console.log(array); // [2, 1, 4, 0, 3, 5, 6, 7, 8, 9] | ||
``` | ||
|
||
# | ||
|
||
### Real World Examples | ||
|
||
1. Randomizing Quiz Questions - Use Case **Present questions in a random order.** | ||
```js | ||
function shuffle(array) { /*...*/ } | ||
const questions = ['Q1', 'Q2', 'Q3', 'Q4']; | ||
const shuffledQuestions = shuffle(questions); | ||
``` | ||
2. Shuffling Playlist Songs - Use Case **Play songs in a random order.** | ||
```js | ||
function shuffle(array) { /*...*/ } | ||
const playlist = ['song1.mp3', 'song2.mp3', 'song3.mp3']; | ||
const shuffledPlaylist = shuffle(playlist); | ||
``` | ||
3. Randomizing Survey Responses - Use Case **Minimize order bias in survey options.** | ||
```js | ||
function shuffle(array) { /*...*/ } | ||
const responses = ['Strongly Agree', 'Agree', 'Neutral', 'Disagree', 'Strongly Disagree']; | ||
const shuffledResponses = shuffle(responses); | ||
``` | ||
|
||
# | ||
|
||
### Reference | ||
|
||
[Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) | ||
<br /> | ||
[Shuffle under-the-hood](https://javascript.info/task/shuffle) |