JavaScript PR

JavaScript – 配列の要素をランダムにシャッフルする方法

javascript-array-random-shuffle
記事内に商品プロモーションを含む場合があります

2024/9/4 加筆・修正しました

本記事では、
JavaScriptでRandomな数値を
生成する方法を解説します!

  • Math.floor()/Math.random()によるランダムな整数生成
  • 生成したランダムな整数を使って、
    配列の要素のシャッフル位置を決める

ランダムな整数生成

Math.floor(Math.random() * intArr.length)の詳細解説

let intArr = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
const tempIndex = Math.floor(Math.random() * intArr.length);

このコードは、配列 intArr の長さ内でランダムな整数を生成するために使用されます。
それぞれの関数と演算子は以下のように機能します:

  1. Math.random():
    0以上1未満のランダムな浮動小数点数を生成します。
    例えば、0.5、0.623、0.999などです。
  2. Math.random() * intArr.length:
    Math.random() によって生成された数値に配列の長さを掛けることで、
    0から配列の長さ(この例では11)未満のランダムな数値を生成します。
    例えば、配列の長さが11の場合、0から10.999…までの数値が得られます。
  3. Math.floor():
    この関数は数値を小数点以下で切り捨てて、最も近い整数にします。
    したがって、Math.floor(Math.random() * intArr.length) は、
    0から配列の長さ(この例では11)の一つ小さい数値(この例では10)
    までのランダムな整数を生成します。

応用↓ 範囲指定でランダム生成

// 任意の範囲でランダムな整数を生成する
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

console.log(getRandomInt(5, 20)); // 5から20までのランダムな整数

他の使い方の例

  1. ランダムな数値の生成:
    汎用的に任意の範囲(例:1から100)でランダムな整数を生成することができます。
    const randomNum = Math.floor(Math.random() * 100) + 1;
  2. 配列からランダムな要素を選択:
    任意の配列からランダムに要素を選ぶことができます。
    const fruits = ["apple", "banana", "cherry", "date"]; 
    const randomFruit = fruits[Math.floor(Math.random() * fruits.length)];
  3. ゲームやクイズでのランダムな質問選択:
    複数の質問や選択肢の中からランダムに一つを選ぶ際に使えます。
    const questions = ["Q1", "Q2", "Q3", "Q4"]; 
    const randomQuestion = questions[Math.floor(Math.random() * questions.length)];

これらの例は、Math.random()Math.floor() を組み合わせることで、
様々な状況でランダムな値を生成するために応用できます。

配列のシャッフル

JavaScriptを使って配列内の要素をランダムに並び替える方法は、
多くのアプリケーションやゲームで便利に使えます。
この記事では、JavaScriptで配列の要素を効率的にシャッフルする方法を紹介します。

シャッフルする配列

まず、シャッフルしたい要素を持つ配列を定義します。
この例では、0から100までの10刻みの整数を要素とする配列 intArr を使用します。

let intArr = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100];

シャッフルのプロセス

シャッフル処理では、配列の各要素について以下のステップを実行します。

  1. ランダムなインデックスの生成:
    Math.random() 関数を使用して、
    配列の長さを最大値とするランダムなインデックスを生成します。
    このインデックスは、交換対象の要素を選ぶために使用されます。
  2. 要素の交換:
    現在の要素とランダムに選ばれた要素の位置を交換します。

シャッフルの実装

以下のコードは、配列のシャッフルを実行します。

let intArr = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
console.log(intArr); // シャッフル前の配列を表示

for (const [index, num] of intArr.entries()) {
    const tempIndex = Math.floor(Math.random() * intArr.length); // ランダムなインデックス
    const tempNum = intArr[index]; // 現在の要素を一時的に保存
    intArr[index] = intArr[tempIndex]; // 現在の要素とランダムな要素を交換
    intArr[tempIndex] = tempNum; // 一時的に保存した要素をランダムな位置に配置
}

console.log(intArr); // シャッフル後の配列を表示

シャッフルの具体例

数値配列

let numbers = [1, 2, 3, 4, 5];
console.log(safeShuffle(numbers)); // シャッフルされた数値配列

文字列配列

let fruits = ["apple", "banana", "cherry", "date"];
console.log(safeShuffle(fruits)); // シャッフルされた文字列配列

オブジェクト配列

let menuItems = [
  { id: 1, name: "Coffee" },
  { id: 2, name: "Tea" },
  { id: 3, name: "Juice" }
];
console.log(safeShuffle(menuItems)); // シャッフルされたオブジェクト配列

シャッフルの注意点

シャッフルを行う際には、
配列が空でないか、
または予期しない型のデータが
含まれていないかを確認することが重要です。

以下はその確認例です。

  1. 配列が空の場合の対応
    シャッフル前に、
    配列が空でないかをチェックする。

    空の場合は何もせずにそのまま返す。
  2. 予期しない型への対応
    配列の要素が
    意図した型であるかを事前に確認し、
    問題があればエラーメッセージを表示する。
function safeShuffle(array) {
    if (!Array.isArray(array) || array.length === 0) {
        console.error("無効な配列です");
        return array;
    }

    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        if (typeof array[i] !== typeof array[j]) {
            console.error("異なる型の要素があります");
            continue;
        }
        [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
}

let numbers = [1, 2, 3, 4, 5];
console.log(safeShuffle(numbers)); // 安全にシャッフルされた配列

シャッフルの用途に応じた実装方法

特定の範囲内でシャッフル

配列の一部のみをシャッフルする場合や、
特定の要素を特定の位置に固定したまま
他の要素をシャッフルする方法について説明します。

function partialShuffle(array, startIndex, endIndex) {
    if (startIndex < 0 || endIndex >= array.length || startIndex >= endIndex) {
        console.error("無効なインデックス範囲です");
        return array;
    }

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

let letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
console.log(partialShuffle(letters, 2, 5)); // 部分的にシャッフルされた配列

グループごとにシャッフル

配列を特定のグループに分けてから、
それぞれのグループ内でシャッフルするケース。

この場合、グループ間での順序を保つか、
崩すかを選択する必要があります。

function safeShuffle(array) {
    // 配列が正しいかどうかを確認
    if (!Array.isArray(array) || array.length === 0) {
        console.error("無効な配列です。配列であることを確認してください。");
        return array;
    }

    // 配列内の要素が同じ型かどうかをチェック
    let elementType = typeof array[0];
    for (let i = 1; i < array.length; i++) {
        if (typeof array[i] !== elementType) {
            console.warn("配列に異なる型の要素が含まれています。");
            break;
        }
    }

    // Fisher-Yatesアルゴリズムによるシャッフル
    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 numbers = [1, 2, 3, 4, 5];
console.log(safeShuffle(numbers)); // 安全にシャッフルされた配列


function groupShuffle(array, groupSize) {
    if (groupSize <= 1) {
        console.error("グループサイズは2以上でなければなりません");
        return array;
    }

    let shuffledArray = [];
    for (let i = 0; i < array.length; i += groupSize) {
        let group = array.slice(i, i + groupSize);
        group = safeShuffle(group); // グループ内でシャッフル
        shuffledArray.push(...group);
    }
    return shuffledArray;
}

let items = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(groupShuffle(items, 3)); // グループごとにシャッフルされた配列

シャッフル後の検証方法

シャッフル後結果を検証。

具体的には、
次のようなテストを行うことが推奨されます。

  1. シャッフル結果の均一性チェック
    何度かシャッフルを行い、
    結果が均一に分布しているかを確認する。
    特定のパターンが繰り返されていないかをチェックします。
  2. シャッフル結果の再現性
    同じシード値を用いたシャッフルで
    同じ結果が得られるかを確認することで、
    意図しないバグを防ぎます。
function shuffleWithSeed(array, seed) {
    let random = mulberry32(seed);
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
}

function mulberry32(seed) {
    return function() {
        var t = seed += 0x6D2B79F5;
        t = Math.imul(t ^ t >>> 15, t | 1);
        t ^= t + Math.imul(t ^ t >>> 7, t | 61);
        return ((t ^ t >>> 14) >>> 0) / 4294967296;
    }
}

let items = [1, 2, 3, 4, 5];
console.log(shuffleWithSeed([...items], 123)); // シード123でシャッフル
console.log(shuffleWithSeed([...items], 123)); // 同じシードで再度シャッフルすると同じ結果
console.log(shuffleWithSeed([...items], 456)); // 異なるシードでシャッフルすると異なる結果

応用:Fisher-Yatesアルゴリズム

Fisher-Yatesアルゴリズムは、
効率的で信頼性の高い
配列シャッフルの方法として広く使われています。

function shuffleArray(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;
}


Fisher-Yatesアルゴリズムの特徴は、
ループの中で現在の要素とそれ以前の要素のどれかと交換するため、
偏りのないシャッフルを実現できる点です。

また、時間効率がよく、
時間計算量O(n)で動作するため、
大きな配列に対しても適切です。

これと対照的に、前述の「シャッフルの実装」のような
単純な交換方式は不均一なシャッフル結果を生む可能性があります

まとめ

今回は、
JavaScriptで配列の要素をランダムにシャッフルする方法を紹介しました。

まず、
Math.random()Math.floor()
組み合わせてランダムな数値を生成し、
配列のシャッフルを行う基本的な方法を紹介。

次に、シャッフルしたい配列を準備し、
ランダムなインデックスを生成して要素を交換する方法を解説しました。

また、配列シャッフルで信頼性の高い
Fisher-Yatesアルゴリズムも紹介し、
偏りのないシャッフルの実現方法を説明しています。

シャッフルの実装や効率性についても触れていますので、
皆さんの状況に合わせて、使い分けてください!

皆さんの参考になれば幸いです!