【JavaScript】配列の偏りのないシャッフル!「フィッシャー・イェーツ」のアルゴリズムと使い方

JavaScriptで配列の中身をランダムに並び替えたい時、あなたはどう書いていますか? 手軽な方法として array.sort(() => Math.random() - 0.5) という書き方がよく紹介されますが、実はこの方法だと「並び替えの結果に偏りが出てしまう(完全にランダムにならない)」という致命的な弱点があります。

そこで推奨されるのが、「フィッシャー・イェーツのシャッフル(Fisher-Yates shuffle)」と呼ばれるアルゴリズムです。 名前を聞くと難しそうですが、数行の関数を用意するだけで誰でも簡単に実装できます! 今回は、ボタンを押すたびに配列を偏りなくシャッフルするデモと一緒に、コピペで使えるコードをご紹介します。

デモ動作

See the Pen フィッシャー・イェーツ シャッフルデモ by mikanbako (@mikanbako) on CodePen.

全画面でのデモ動作は以下のリンクからご確認下さい!(別タブで開かれます)

実装コード紹介

【HTML】

<div class="l-wrap">
  <h2>フィッシャー・イェーツ シャッフルデモ</h2>
  <p class="p-desc">ボタンを押すと、配列の中身が偏りなくランダムに並び替わります!</p>
  
  <div class="p-demo-box">
    <!-- 操作ボタン -->
    <button class="c-btn js-shuffle-btn">🔄 ランダムにシャッフル!</button>
    
    <!-- リスト表示エリア -->
    <ul class="c-list js-list">
      <!-- ここにJSでリストが入ります -->
    </ul>
  </div>
</div>

【CSS】

/* ベース装飾 */
.l-wrap {
  padding: 40px 20px;
  max-width: 500px;
  margin: 0 auto;
  text-align: center;
  color: #3e3e3e;
}

h2 {
  margin-bottom: 10px;
  font-size: 16px;
  color: #1bb4d4;
}

.p-desc {
  margin-bottom: 30px;
  font-size: 16px;
  font-weight: bold;
  color: #666;
  line-height: 1.6;
}

.p-demo-box {
  padding: 30px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 4px 15px rgb(0 0 0 / 5%);
}

/* ボタンとリストの装飾 */
.c-btn {
  padding: 12px 24px;
  margin-bottom: 20px;
  font-size: 18px;
  font-weight: bold;
  color: #fff;
  cursor: pointer;
  background: #e83e8c;
  border: none;
  border-radius: 8px;
  transition: 0.3s;
}

.c-btn:hover {
  opacity: 0.8;
  transform: translateY(-2px);
}

.c-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.c-list li {
  padding: 15px;
  font-size: 18px;
  font-weight: bold;
  color: #1bb4d4;
  background: #e0f7fa;
  border: 2px dashed #1bb4d4;
  border-radius: 6px;
}

【JavaScript】

// 💡 シャッフルしたい配列のデータ
const items = ["🍣 お寿司", "🍖 焼肉", "🍜 ラーメン", "🍛 カレー", "🍕 ピザ", "🍚 ごはん"];

// DOM要素の取得
const listContainer = document.querySelector('.js-list');
const shuffleBtn = document.querySelector('.js-shuffle-btn');

// 💡 フィッシャー・イェーツのシャッフル関数!
// (この関数をそのままコピペして使い回せます)
function shuffleArray(array) {
  // 配列を直接書き換えないように、コピーを作成する
  const cloneArray = [...array];
  
  for (let i = cloneArray.length - 1; i > 0; i--) {
    // 0 から i までのランダムなインデックスを取得
    const j = Math.floor(Math.random() * (i + 1));
    // 要素を入れ替える
    [cloneArray[i], cloneArray[j]] = [cloneArray[j], cloneArray[i]];
  }
  
  return cloneArray;
}

// 画面にリストを表示する関数
function renderList(array) {
  listContainer.innerHTML = ""; // 一旦リセット
  array.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item;
    listContainer.appendChild(li);
  });
}

// 初回読み込み時の表示
renderList(items);

// ボタンをクリックした時の処理
shuffleBtn.addEventListener('click', () => {
  // シャッフル関数を実行して、新しい配列で画面を更新!
  const shuffledItems = shuffleArray(items);
  renderList(shuffledItems);
});

コード解説

なぜ sort() ではダメなの?

一番手軽に書ける array.sort(() => Math.random() - 0.5) は、ブラウザに搭載されているソート(並び替え)の内部アルゴリズムに依存してしまいます。そのため、要素の位置によって選ばれる確率が変わり、「完全に均等なランダム」にはなりません。

フィッシャー・イェーツの仕組み(超ざっくり)

// 💡 フィッシャー・イェーツのシャッフル関数!
// (この関数をそのままコピペして使い回せます)
function shuffleArray(array) {
  // 配列を直接書き換えないように、コピーを作成する
  const cloneArray = [...array];
  
  for (let i = cloneArray.length - 1; i > 0; i--) {
    // 0 から i までのランダムなインデックスを取得
    const j = Math.floor(Math.random() * (i + 1));
    // 要素を入れ替える
    [cloneArray[i], cloneArray[j]] = [cloneArray[j], cloneArray[i]];
  }
  
  return cloneArray;
}

このアルゴリズムの仕組みはとてもシンプルです。 「配列の一番後ろの要素」と「それより前にあるランダムな要素」を入れ替える、という作業を、後ろから前へ向かって順番に繰り返しているだけです。 [cloneArray[i], cloneArray[j]] = [cloneArray[j], cloneArray[i]]; という行で、2つの要素を交換しています。

これにより、すべての要素が完全に平等な確率でシャッフルされるため、ランダム機能を作る際の世界的な大定番となっています!

補足:参考リンク

・Wikipedia: フィッシャー–イェーツのシャッフル

まとめ

今回は配列を偏りなくシャッフルする「フィッシャー・イェーツのアルゴリズム」をご紹介しました。

クイズアプリや、おみくじ機能、おすすめ商品のランダム表示など、「公平なランダム性」が求められる機能を作る際には必須のテクニックです。

関数として一つ用意しておけばどんなプロジェクトでも使い回せるので、ぜひご自身のお気に入りスニペットに追加しておいてくださいね!

Prev