Skip to main content

JavaScript: Shuffling Array Elements

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.

Clown bloke

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 to newArray. Then delete/remove that particular element from the clonedArray.

    For instance this is the initial clonedArray:

    First loop, it generates random index of 1.

    That means the element "b" (index 1) is picked from the clonedArray.

    Then we put that picked element to the newArray then delete that element from clonedArray.

    And after that, the loop continues, we get another random index as 1, no problem, because the clonedArray 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 the newArray 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:

Benchmark result

And this is the BOSS technique + same sequence avoidance — so the output will not be similar as the reference input.

Comments

Monkey Raptor uses cookies or biscuits 🍪 for analytics, functionality, and advertisements. More info in Privacy Policy