🦁

配列の要素をランダムにシャッフルさせたい

に公開
4

やりたいこと

JavaScriptの配列の要素を呼び出すたびに要素数を変えずに、ランダムな順番の配列を生成する関数を生成したい

ユースケース

ブラウザをリロードするたびにリストの順番を、ランダムに入れ替えたい時なんかに使用できます。

たとえば、ホテルの情報をリストで表示しているサイトがあったときに、いつも特定のホテルだけが一番上に表示されていたら、そのホテルだけにアクセスやクリックが集中してしまいます。

もし他のホテル会社さんが、後発組として、そのサイトに情報を掲載したとしても、シャッフルの実装がされていなければ、ずっとそのホテルの情報が一番下に情報が表示されてしまいます。

そんなサイトでは、後発組として情報を掲載する意味がありませんよね。

みたいなサイトの開発を担当したときに、以下に紹介した要素の順番を乱数を用いて並び替える実装を用います。

実現方法

今回は、以下の2つを用いた実装方法を紹介します。

  • for文を用いて実装する方法
  • reduce関数を用いて実装する方法

for文を用いた実装方法

/**
 * 配列の要素の値を変更せずに要素の順番を並べ替える関数
 * (e.g) [0,1,2,3,4] => [1,3,2,0,4]
 */
 const shuffleArray = (array) => {
    const cloneArray = [...array]

    for (let i = cloneArray.length - 1; i >= 0; i--) {
      let rand = Math.floor(Math.random() * (i + 1))
      // 配列の要素の順番を入れ替える
      let tmpStorage = cloneArray[i]
      cloneArray[i] = cloneArray[rand]
      cloneArray[rand] = tmpStorage
    }

    return cloneArray
  }

// 要素が数値の配列でもいける
console.log(shuffleArray([0,1,2,3,4]));
// expected output : [0,3,4,1,2]

// 要素が文字列の配列でもいける
console.log(shuffleArray(['JavaScript','React','Vue']));
// expected output : ['React','JavaScript','Vue']

// 要素がオブジェクトの配列でもいける
console.log(shuffleArray([{id:1,age:23},{id:2,age:24},{id:3,age:25}]));
// expected output : [{id:2,age:24},{id:1,age:23},{id:3,age:25}]

reduce関数を用いた実装方法

// reduceを用いた実装方法
const shuffleArray = (array) => {
  const cloneArray = [...array];

  const result = cloneArray.reduce((_,cur,idx) => {
    let rand = Math.floor(Math.random() * (idx + 1));
    cloneArray[idx] = cloneArray[rand]
    cloneArray[rand] = cur;
    return cloneArray
  })

  return result;
}

console.log(shuffleArray([1,2,3,4,5]))
// expected output : [3,2,5,1,4]

簡単に解説

Math.floor()

実引数に与えられた数値以下の「整数」を返します

console.log(Math.floor(5.95))
// 出力結果 : 5

console.log(Math.floor(6.78))
// 出力結果 : 6

console.log(Math.floor(7.53))
// 出力結果 : 7

Math.random()

0以上1未満の「乱数」を返します

console.log(Math.random())
// 出力結果 : 0.10693731104538573

console.log(Math.random())
// 出力結果 : 0.38645632869127056

console.log(Math.random())
// 出力結果 : 0.9264442420785377

上記、2つを用いれば、ランダムな整数を返すことが実現できます!

最後に

他にもこんな良い実装方法があるよ、とかもし知見のある強い方がいらっしゃったらコメントいただけると嬉しいです!!

Discussion

renadachirenadachi

下記は典型的・古典的な誤った実装です。fukken sanありがとうございます。

const shuffleArray = (array) => {
  return array.slice().sort(() => Math.random() - Math.random())
}
h4lh4l

sortをこんな感じで使う方法は思いつきませんでした。ありがとうございます!

2024年10月現在だと Array.prototype.toSortedが使えそうなので、破壊的変更を考慮することなくかけそうですね。

const shuffleArray = (array) => {
  return array.toSorted(() => Math.random() - Math.random())
}
fukkenfukken

コメントのものは、典型的・古典的な誤った実装です(記事本文のものは妥当です)。
例えば3要素を並べ替える場合、元通りになる([a, b, c] → [a, b, c])確率が3割を超えます(6通りなので、16.6%程度になるのが正しい)。
シャッフルする文脈で sort() を使うアルゴリズムは大抵間違っています。シャッフルは自分で実装しないか、するとしても最低限、確認をした方が良いでしょう。

    const shuffleArray = (array) => {
      return array.slice().sort(() => Math.random() - Math.random());
    };
    const test = (sortFn, input, times) => {
      const result = new Map();
      for (let i = 0; i < times; i++) {
        const key = sortFn(input).join('');
        result.set(key, (result.get(key) ?? 0) + 1);
      }
      console.table(Array.from(result.entries()));
    };
    test(shuffleArray, ['a', 'b', 'c'], 10000);
renadachirenadachi

fukkenさん、ご指摘ありがとうございます!
たしかに、最初の要素が元の位置に固定されやすいですね。