Shuffle is different than random in term. But indeed randomness is applied in shuffling.
As in shuffling cards and giving out one random card, those are different.
Algorithmically, we can loop through the reference array, generate random index based on the reference array length, then put that item (with that random index) to a new array.
But since random
can generate similar number (being floored or rounded down) as the previous one, that can lead to duplicates.
For Example
We Have Input Array:
⚠️ If we simply loop through the array while creating a random index then blindly 🙈 copy the randomly chosen item to a new array, surely there will be duplicates.
❌ Unwanted Output:
🚮 Deletion Technique
We need to trim (remove the element from) the array reference after one element gets picked by the random index.
Example array input:
Then we want to shuffle the array input.
-
Create a clone of the original array (
clonedArray
), so that nothing will happen to the original one.[✒️ Side note] This is a particular post for copying object in JavaScript.
Then create an empty array (
newArray
) to store the shuffled elements. -
Loop through the
clonedArray
to randomly pick an element from it, then place that chosen element tonewArray
. Then delete/remove that particular element from theclonedArray
.For instance this is the initial
clonedArray
:First loop, it generates random index of
1
.That means the element
"b"
(index1
) is picked from theclonedArray
.Then we put that picked element to the
newArray
then delete that element fromclonedArray
.And after that, the loop continues, we get another random index as
1
, no problem, because theclonedArray
is different now.So, the next picked element (with index
1
) from["a", "c", "d"]
is"c"
.Now we have:
And so on.
As you can see in that looping sequence, the
clonedArray
length will decrease until it's empty, and thenewArray
length will increase and it will finally have the shuffled items.
🧑💻 Code
This is my original code back in 2015 (first published) with minor adjustment of camel casing the variable names and default function parameter (arr = []
).
The original array, with the variable arr
, will be left intact.
That shuffle
function above can be cascaded, like so:
To shuffle the original array four times.
Using do-while
, while
, and forEach
Each of those will behave similarly as the first code snippet.
🏆 Fisher-Yates BOSS 😎 Technique
This technique is the most efficient, introduced by Richard Durstenfeld in 1964. This is the description on Wikipedia.
Let's take the snippet from Wikipedia. Back then, it looked like below — now, it is already updated. 🤣 Let's keep it that way:
The technique is straightforward, get random index then swap.
But as you can see above, the snippet (before the update on Wikipedia) doesn't return anything. It's just snippet of the technique.
This is the proper vibe.
That is actually EFFICIENT! 😲 🥳
With a little overhead with the slice
— because I don't want to mutate the original array.
The problem with pure randomness, the shuffled array (output) can have similar sequence as the input.
At some point, this input:
Can be shuffled as:
We want to ensure the generated sequence always differs from the original input.
We can add if
condition within the loop, and we need while
to accomplish it, as such:
I implement while
so that there is no extra overhead inside the loop. I am only interested in simple boolean check within the loop.
The function shuffleItReal
above will act as efficient as fisherYatesShuffle
, but with addition that it will always generate different sequence than the reference array.
A Bit of Analysis 🧐
Probability of returning to the original order becomes $0$ — instead of $1/{n!}$
For $n = 2$, it always reverses: [a, b]
➡️ [b, a]
with probability $1$ (100%).
Standard Fisher–Yates has expected fixed points $= 1$. In this, every position except the last can still have fixed points. The last element can never be a fixed point, because I block that option right at the start. Thus, fixed points drop slightly. It's like a drill sergeant shouting Oi, last bloke, no sitting around — get moving!
We can call this algorithm as Deranged Fisher–Yates (last-element edition).
🏃➡️🏃♀️➡️🏃♂️➡️ Benchmark
Let's make a default array with 10,000 items, invoke each technique 1,000 times, and measure the completion duration of it.
My machine was Lenovo Legion — 15ACH6 while running the tests below. It will be different for different machine, but I believe it will still have the similar ranking for the benchmark results.
This is the snapshot of the results:
And this is the BOSS technique + same sequence avoidance — so the output will not be similar as the reference input.
Comments
Post a Comment