[JavaScript] Completely Randomize Arrays! Implementing the Fisher-Yates Shuffle

目次

Overview

There are many situations where you want to randomize the order of data, such as the order of questions in a quiz app, a bingo game, or the presentation order for a meeting. Simply using Math.random() often results in “bias,” meaning the shuffle is not truly fair. In this article, we will explain how to implement a standard algorithm whose fairness is mathematically proven: the Fisher-Yates Shuffle.

Specifications (Input/Output)

Fisher-Yates Shuffle

This algorithm works by iterating through the array from the end to the beginning and swapping the element at the “current position” with an element at a “random position” before it.

  • Input: An array (any data type).
  • Output: A randomized array.
  • Complexity: $O(n)$ (extremely fast).

Methods to Avoid

You may often see the method array.sort(() => Math.random() - 0.5). Technically, this does not result in a true random distribution and introduces bias. You should avoid using this method in professional projects.

Basic Usage

It is best to define a general-purpose shuffle function for reuse.

/**
 * Function to shuffle an array (Fisher-Yates Shuffle)
 * @param {Array} sourceArr - The original array
 * @returns {Array} A new shuffled array
 */
const shuffleArray = (sourceArr) => {
    // Create a copy using spread syntax to avoid mutating the original
    const array = [...sourceArr];
    
    // Loop from the end of the array to the beginning
    for (let i = array.length - 1; i >= 0; i--) {
        // Generate a random index between 0 and i
        const randomIndex = Math.floor(Math.random() * (i + 1));
        
        // Swap elements using destructuring assignment
        [array[i], array[randomIndex]] = [array[randomIndex], array[i]];
    }
    
    return array;
};

// Usage example
const numbers = [1, 2, 3, 4, 5];
const shuffled = shuffleArray(numbers);
console.log(shuffled); // Example: [3, 1, 5, 2, 4]

Full Code (HTML / JAVASCRIPT)

This is a tool for determining the “speaker order” for an internal Lightning Talk (LT) event. A fairly reordered list is generated every time you click the button.

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LT Order Shuffle</title>
    <style>
        .shuffle-container {
            font-family: 'Segoe UI', sans-serif;
            max-width: 400px;
            padding: 25px;
            border: 1px solid #ddd;
            border-radius: 8px;
            background-color: #fafafa;
            text-align: center;
        }
        h3 { margin-top: 0; color: #444; }
        
        .speaker-list {
            list-style: decimal;
            padding-left: 1.5em;
            text-align: left;
            margin: 20px auto;
            width: fit-content;
            font-size: 1.1rem;
        }
        .speaker-item {
            padding: 5px 0;
            border-bottom: 1px dashed #ccc;
        }
        
        .btn-shuffle {
            background-color: #e91e63;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 30px;
            font-size: 1rem;
            font-weight: bold;
            cursor: pointer;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            transition: transform 0.1s, box-shadow 0.1s;
        }
        .btn-shuffle:active {
            transform: translateY(2px);
            box-shadow: none;
        }
    </style>
</head>
<body>

<div class="shuffle-container">
    <h3>LT Speaker Order</h3>
    
    <ol id="order-list" class="speaker-list">
        </ol>
    
    <button id="btn-shuffle" class="btn-shuffle">Shuffle Order 🎲</button>
</div>

<script src="lt_shuffle.js"></script>
</body>
</html>

JavaScript

/**
 * Speaker Order Shuffle Script
 * Implementation of the Fisher-Yates algorithm
 */

// 1. Speaker list (Initial data)
const speakers = [
    'Mori',
    'Komori',
    'Nakamori',
    'Omori',
    'Hayashi',
    'Kobayashi',
    'Nakabayashi',
    'Obayashi'
];

// DOM elements
const listElement = document.getElementById('order-list');
const shuffleButton = document.getElementById('btn-shuffle');

/**
 * Function to randomize array order
 * @param {Array} array target array
 * @returns {Array} A new shuffled array
 */
const shuffleArray = (array) => {
    // Create a copy to avoid destructive operations
    const clone = [...array];

    for (let i = clone.length - 1; i >= 0; i--) {
        // Determine a random index
        const rand = Math.floor(Math.random() * (i + 1));
        
        // Swap elements with destructuring assignment
        [clone[i], clone[rand]] = [clone[rand], clone[i]];
    }
    
    return clone;
};

/**
 * Function to render the list
 * @param {Array} items array to display
 */
const renderList = (items) => {
    listElement.innerHTML = '';
    
    items.forEach((item) => {
        const li = document.createElement('li');
        li.className = 'speaker-item';
        li.textContent = `${item}`;
        listElement.appendChild(li);
    });
};

/**
 * Handler for button click
 */
const onShuffleClick = () => {
    // Execute shuffle
    const newOrder = shuffleArray(speakers);
    
    // Update display
    renderList(newOrder);
};

// Initial render
renderList(speakers);

// Event listener
shuffleButton.addEventListener('click', onShuffleClick);

Custom Points

Adding animations where elements move around during the shuffle can significantly improve the User Experience (UX). If you have specific roles that must be fixed, such as the “Opening Greeting” or “Closing Remarks,” you can implement logic to exclude those elements from the shuffle and then join them back at the end.

Important Notes

The code provided creates a copy using [...array], which is safe. However, if you manipulate the input array directly without copying it, the original data at the source will also change. This is known as a destructive change. Furthermore, Math.random() generates pseudo-random numbers. If you require a very high level of security for things like password generation or casino games, you must use crypto.getRandomValues().

Advanced Usage

Verifying the Bias of Sort-based Shuffling

This code demonstrates why using sort for shuffling is not recommended.

// Note: This method is not recommended
const badShuffle = (arr) => arr.sort(() => Math.random() - 0.5);

// In many browsers, this results in certain patterns appearing more frequently.

Summary

When you need to shuffle an array, the golden rule is to use the Fisher-Yates Shuffle instead of self-made implementations or sort hacks. This algorithm involves iterating from the end of the array and swapping elements randomly using a for loop, Math.random(), and destructuring assignment. To ensure safety in your applications, always perform these operations on a copy of the original array to avoid destroying the source data. By keeping this function in your toolkit, you can handle any requirement for random data display with confidence and fairness.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次