Shuffling Arrays in JavaScript

Introduction

One of the many features that JavaScript provides is the ability to easily work with arrays. An array is a special type of object that is used to store multiple values in a single variable, and in the case of JavaScript, these values do not need to be the same type. At times, you may find yourself needing to randomize the order of elements in an array, a.k.a shuffling.

This article will guide you through various methods of shuffling arrays in JavaScript.

Arrays in JavaScript

Before we dive into the shuffling methods, let's first understand what an array is in JavaScript. An array is a data structure that can store multiple values in a single variable. Each value (also known as an element) in an array has a numeric position, known as its index, which starts from 0.

Here's an example of a simple JavaScript array:

let fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
console.log(fruits[0]); // Output: 'Apple'

In this array, 'Apple' is at index 0, 'Banana' is at index 1, and so on. You can access the elements of an array by referring to the index number.

Note: In JavaScript, arrays are dynamic, meaning they can change size at runtime, and they can contain elements of any type - numbers, strings, objects, or even other arrays.

Arrays in JavaScript come with a suite of built-in methods that allow you to manipulate the array and its elements. Some of these methods include push() to add elements, pop() to remove elements, sort() to sort elements, and so on.

However, there is no built-in method for shuffling the elements of an array. This is where the techniques we'll discuss in the following sections come in handy.

Why shuffle an array?

Shuffling an array is an operation that randomly rearranges the elements in an array. This is particularly useful in a variety of scenarios.

For example, if you're making a card game, you'll need to shuffle the deck of cards stored in an array before the game starts to ensure fairness. In machine learning, it's common to shuffle the input data before splitting it into training and test sets to ensure that the model is not influenced by any order present in the data.

Shuffling can also be used to generate a random sample from a larger dataset, or to create a more visually appealing display of items on a web page. In JavaScript, there is no built-in function to shuffle an array, but there are several well-known algorithms that can be implemented fairly easily, which we'll take a look at.

The Fisher-Yates (Knuth) Shuffle

The Fisher-Yates Shuffle, also known as the Knuth Shuffle, is a simple and efficient method to shuffle an array. It works by iterating through the array from the last element to the first, swapping each element with an element at a random index less than or equal to its current index.

Here is a JavaScript implementation of the Fisher-Yates Shuffle:

function fisherYatesShuffle(array) {
    let currentIndex = array.length;
    while (currentIndex !== 0) {
        // Pick a remaining element
        let randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex--;

        // And swap it with the current element
        let temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;
    }
    return array;
}

let arr = [1, 2, 3, 4, 5];
console.log(fisherYatesShuffle(arr)); // [ 3, 1, 5, 4, 2 ]

When you run this code, it will print a shuffled version of the array [1, 2, 3, 4, 5]. The output will be different each time you run the function, as it should be with a good shuffle algorithm.

Note: The Fisher-Yates Shuffle is an in-place algorithm, which means it shuffles the original array without creating a new one. This makes it a very space-efficient method of shuffling an array.

The Fisher-Yates Shuffle has a time complexity of O(n), which makes it a very efficient method of shuffling large arrays. This is because it only needs to make one pass through the array, swapping each element with a randomly chosen element that hasn't been shuffled yet.

Durstenfeld Shuffle

The Durstenfeld shuffle is a computer-optimized version of the Fisher-Yates shuffle, developed by Richard Durstenfeld in 1964. This algorithm is designed to be more efficient and works by picking one random element for each original array element, and then excluding it from the next draw.

Let's see how we can implement this in JavaScript:

function durstenfeldShuffle(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
}

let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(durstenfeldShuffle(arr));

When you run this script, you'll see the array elements are shuffled randomly. Again, each execution will likely produce a different result.

Note: The Durstenfeld shuffle is considered more efficient than the Fisher-Yates shuffle because it avoids the need to insert the selected elements into the new array, which can be computationally expensive.

Schwartzian Transform

The Schwartzian Transform, named after Randal L. Schwartz, is a sorting technique used in computer programming. It's not exactly a shuffle algorithm, but it can be adapted for this purpose. The idea is to transform the array in a way that makes sorting more efficient, then undo the transformation.

In the context of shuffling an array, we can use the Schwartzian Transform to assign a random number to each array element, sort the array based on these numbers, and then remove the numbers, leaving a shuffled array.

Here's a JavaScript implementation:

function schwartzianShuffle(array) {
    return array
        .map((a) => [Math.random(), a]) // Assign a random number to each element
        .sort((a, b) => a[0] - b[0]) // Sort by the random numbers
        .map((a) => a[1]); // Remove the random numbers
}

let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(schwartzianShuffle(arr));

When you run this script, you'll see the array elements are shuffled randomly. As with the previous algorithms, each execution will likely produce a different result.

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

While the Schwartzian Transform is a clever technique, it may not be the most efficient method for shuffling large arrays in JavaScript. However, it serves to demonstrate how you can employ other techniques to achieve a desired result.

Shuffling Multidimensional Arrays

When it comes to shuffling multidimensional arrays, the process is a bit more complex. A multidimensional array is basically just an array of arrays. If we were to simply apply a shuffle algorithm to a multidimensional array, we would end up shuffling the sub-arrays, but not the individual elements within those sub-arrays.

One way to properly shuffle a multidimensional array is to flatten the array, shuffle it, and then rebuild the multidimensional array. Here's how you can do it:

function shuffleMultiDimensionalArray(array) {
    // Flatten the array
    let flattened = [].concat(...array);

    // Shuffle the flattened array
    for (let i = flattened.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [flattened[i], flattened[j]] = [flattened[j], flattened[i]];
    }

    // Rebuild the multidimensional array
    let result = [];
    while(flattened.length) result.push(flattened.splice(0, array[0].length));

    return result;
}

let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(shuffleMultiDimensionalArray(array));

This will output a shuffled multidimensional array, like:

[[3, 1, 2], [6, 5, 4], [8, 9, 7]]

Note: This function assumes that all sub-arrays in the multidimensional array are of the same length. If the sub-arrays have different lengths, this function will not work properly.

Using Lodash

If you're using Lodash, a popular JavaScript utility library, you can shuffle arrays with the _.shuffle() function. This function implements the Fisher-Yates shuffle algorithm and works with both single and multidimensional arrays.

Here's how you can shuffle an array using Lodash:

const _ = require('lodash');

let array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(_.shuffle(array));

This will output a shuffled array, like:

[3, 2, 9, 1, 6, 8, 7, 4, 5]

And here's how you can shuffle a multidimensional array:

const _ = require('lodash');

let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
console.log(_.shuffle(_.flatten(array)));

This will output a shuffled array, like:

[3, 2, 1, 6, 5, 4, 9, 8, 7]

Here, the _.flatten() function is used to flatten the multidimensional array before shuffling it. However, it won't rebuild the multidimensional array after shuffling. If you need to maintain the multidimensional structure, you'll have to rebuild it yourself.

Performance Considerations

When it comes to shuffling arrays in JavaScript, performance can be an important factor to consider, especially when dealing with large arrays.

The Fisher-Yates (Knuth) shuffle and its optimized version, the Durstenfeld shuffle, are known for their efficient O(n) time complexity. This means that the time it takes to shuffle the array grows linearly with the size of the array, which is pretty good.

The Schwartzian Transform, on the other hand, is not as efficient. This method involves a sorting operation, which has a time complexity of O(n log n). This makes it slower than the Fisher-Yates and Durstenfeld shuffles for larger arrays.

However, when dealing with smaller arrays, the difference in performance between these methods is negligible. The choice of method will then depend on other factors such as code readability and simplicity.

Note: Always consider the trade-off between performance and code readability. A slightly slower method might be worth it if it makes your code easier to understand and maintain.

Using libraries like Lodash can also have performance implications. While these libraries are optimized and usually perform well, they add an extra layer of abstraction that can slow things down compared to native JavaScript methods.

Here's a comparison of the time taken to shuffle an array of 1 million numbers using different methods:

let arr = Array.from({length: 1000000}, (_, i) => i + 1);

console.time('Fisher-Yates Shuffle');
fisherYatesShuffle(arr);
console.timeEnd('Fisher-Yates Shuffle');

console.time('Durstenfeld Shuffle');
durstenfeldShuffle(arr);
console.timeEnd('Durstenfeld Shuffle');

console.time('Schwartzian Transform');
schwartzianShuffle(arr);
console.timeEnd('Schwartzian Transform');

console.time('Lodash Shuffle');
_.shuffle(arr);
console.timeEnd('Lodash Shuffle');
$ node shuffle.js
Fisher-Yates Shuffle: 19.95ms
Durstenfeld Shuffle: 18.118ms
Schwartzian Transform: 898.175ms
Lodash Shuffle: 21.308ms

The exact times will vary depending on your machine and the current workload, but you should see that the Fisher-Yates and Durstenfeld shuffles are significantly faster than the Schwartzian Transform and slightly faster than the Lodash shuffle.

With regards to being faster than Lodash, this may be due to some overhead with using a library like Lodash.

Conclusion

Shuffling arrays in JavaScript is a common requirement that can be achieved in several ways. While the Fisher-Yates (Knuth) shuffle and the Durstenfeld shuffle are efficient and widely used, other methods like the Schwartzian Transform or using libraries like Lodash might be more suitable depending on the specific requirements and constraints of your project.

Remember to consider performance implications when choosing a method, especially when dealing with large arrays. However, don't forget that code readability and simplicity are also important factors to consider. The best solution is usually those with a balance between efficiency, readability, and simplicity.

Last Updated: August 26th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms