【JavaScript】複数要素の中から特定の数だけランダムに表示

  • URLをコピーしました!
目次

コード

 function randomSelected(array, count) {
    const copyArray = [...array];
    const randomArray = [...Array(count)].map(() => {
      const randomStartIndex = Math.floor(Math.random() * copyArray.length);
      return copyArray.splice(randomStartIndex, 1).at();
    })

    return randomArray;
  }

関数名や変数名が変、コードの稚拙さもあるかもしれませんがお許しください🙇‍♂️

コードの解説

function randomSelected(array,count) { ・・・}

関数宣言でrandomSelected関数を定義しています。

第一引数にランダム表示対象の配列を指定します。

第二引数にランダム表示させたい数を指定します。
例:10個の中から3個ランダムに表示させる場合、countには3を指定します。

const copyArray = [...array];

引数に渡された配列をスプレッド構文でコピーしています。
コピーしないと元の配列が破壊されてしまうためです↓

const original = ["りんご", "ぶどう", "スイカ"];

const copy = original;

copy[1] = "みかん";//ぶどうをみかんに変更

console.log(`original:${original}`); //-> original:'りんご', 'みかん', 'スイカ'
console.log(`copy:${copy}`);//-> copy:'りんご', 'みかん', 'スイカ'

上記の結果を見ると、コピー元の配列originalも「ぶどう」から「みかん」に変更されてしまってます。これを防ぐためにスプレッド構文で配列をコピーしています↓

 const original = ["りんご", "ぶどう", "スイカ"];

 const copy = [...original];//スプレッド構文でコピー

 copy[1] = "みかん"; //ぶどうをみかんに変更

 console.log(`original:${original}`); //-> original:'りんご', 'ぶどう', 'スイカ'
 console.log(`copy:${copy}`); //-> copy:'りんご', 'みかん', 'スイカ'

スプレッド構文を使用すると、配列の長さ分だけ値を展開することができます↓

const array = ["ぶどう", "りんご", "みかん"];
array.length = 1;
console.log([...array]);//-> ["ぶどう"]

また、スプレッド構文で配列をコピーしても
コピー元の配列が破壊される場合があります↓

const original = ["りんご", ["ぶどう", "トマト"], "スイカ"];

const copy = [...original];

copy[1][1] = "みかん";//トマトをみかんに変更

console.log(`original:${original}`);//->original:りんご,ぶどう,みかん,スイカ
console.log(`copy:${copy}`);//->copy:りんご,ぶどう,みかん,スイカ

上記の結果を見ると、コピー元の配列originalの「トマト」が「みかん」に変更されています。
スプレッド構文でコピーした配列の階層が深くなっている値を変更すると、コピー元の配列が破壊されます。

階層が深くなった配列の値を変更してもコピー元が破壊されないようにするには、
structuredClone()を使用します↓

const original = ["りんご", ["ぶどう", "トマト"], "スイカ"];

const copy = structuredClone(original);
      
copy[1][1] = "みかん";

console.log(`original:${original}`);//->original:りんご,ぶどう,トマト,スイカ
console.log(`copy:${copy}`);//->copy:りんご,ぶどう,みかん,スイカ

structuredClone()の対応状況はこちら→“structuredClone” | Can I use… Support tables for HTML5, CSS3, etc

配列のコピーについてはこちらの書籍で詳しく解説されています↓

const randomArray = [...Array(count)].map( () => {・・・})

Array()は、新しく配列を作成するときに使用します。配列リテラル記法で記述することもできますが、今回は配列の長さが countの値によって変わり、動的なのでArray()を使用してます。

リテラル:数値や文字列などを記述するときの構文。” ”や[ ]、{ }など。
静的:固定されている、変動しない
動的:入力内容によって変動する

Array(count)とすることで、countに渡された値の長さのみを持つ空の配列が作成されます↓

//countに「3」が渡された場合
const array = Array(3);

console.log(array)//-> [empty * 3] length:3

空の配列のままでは反復処理できないので、角括弧[]でArray(count)を囲み、
スプレッド構文で配列の長さ分だけ値を展開します↓

const array = [...Array(3)];
console.log(array);//-> [undefined,undefined,undefined]

スプレッド構文を使用せず、fill()を使用しても同様の結果が得られます↓

const array = Array(3).fill();
console.log(array);//-> [undefined,undefined,undefined

map()は、配列を反復処理し、その結果から新しい配列を返すメソッドです。
詳しくはこちら→Array.prototype.map() – JavaScript | MDN

forEach()は反復処理を行うだけで、新しい配列を返さないためmap()を使用しています。

上記からconst randomArray = [...Array(count)].map( () => {・・・}) をまとめると、countの数だけ反復処理を行い、その結果から得られた新しい配列を定数randomArrayに代入するということになります。

const randomStartIndex = Math.floor(Math.random() * copyArray.length);

Math.floor()は、与えられた数値の小数点以下を切り捨てるメソッドです↓

console.log(Math.floor(5.002));//-> 5
console.log(Math.floor(5.6));//-> 5
console.log(Math.floor(-5.6));//-> -6

Math.random()は、0以上 (0は含む) 1未満 (1は含まない) の数値を返します

console.log(Math.random())//-> 0 (0は含む)から1未満 (1は含まない)の値

Math.random() * copyArray.lengthMath.random()にcopyArrayの長さを掛けているので、0からcopyArrayの長さ未満の数値を返します↓

//copyArrayの長さが10の場合
console.log(Math.random() * 10);//-> 0 (0は含む)から10未満 (10は含まない)の値

そして、Math.floor()で全体を囲っているので、小数点以下は切り捨てられます。
その値を定数randomStartIndexに代入しています↓

//copyArrayの長さが10の場合
const randomStartIndex = Math.floor(Math.random() * 10);

console.log(randomStartIndex);//-> 0から9までの値を返す

return copyArray.splice(randomStartIndex, 1).at();

splice()は、開始位置から特定の長さ分の要素を切り取るメソッドです↓

//開始位置が0で、長さが1の場合
const array = ["ぶどう", "りんご", "みかん"];
console.log(array.splice(0, 1));//-> [ぶどう]

上記を踏まえるとcopyArray.splice(randomStartIndex, 1)は、開始位置が0から9までの値を一つ切り取るということになります。

先ほども説明した通りmap()は反復処理をして得た結果から新しい配列を返します。
copyArray.splice(randomStartIndex, 1)は、長さ一つ分の配列を返します。

現状では配列の中に配列を返すので、階層が深くなります↓

//randomSelected関数のコード省略
//取り出した結果がみかんとりんごの場合

const array = ["ぶどう", "りんご", "みかん"];
const random = randomSelected(array, 2);
console.log(random);//-> [ ["みかん"], ["りんご"] ]

階層が深くなるのを防ぐためにat()メソッドを使用します。

at()は、与えられた数の位置の値を取り出すためのメソッドです。
負の数が与えられると配列の末尾から数えます↓

const array = ["ぶどう", "りんご", "みかん"];
console.log(array.at());//-> ぶどう 何も数が与えられないと0番目の値を返す
console.log(arrya.at(-1))//-> みかん
console.log(array.at(-2));//-> りんご

よって、countの数だけcopyArrayの中からランダムな位置の値を一つ取り出す処理を行い、その処理の結果から新しい配列を作成し定数randomArrayに返すということになります。

at()を使用せず、普通に配列の値を取り出す方法( array[0] )でも問題ないです。
使ってみたかっただけなので

return randomArray;

randomSelected関数の返り値として、randomArrayを返しています。

定数randomAarrayに代入せず、いきなりreturnで記述したほうがいいかもしれません。

使用例

Shibajukuのチーム制作で、プレゼント釣リーのゲーム中画面の実装を担当させていただきました。

プレゼントは全部で10個ありますが、
表示するのは5個なのでこのコードを使ってランダムに5個表示させました↓

const imagePath = "./assets/img/common/";

const imageClass = "p-playingGame__present js-present";

const presentBoxes = document.querySelectorAll(".js-presentBox");

const imageElements = [
    `<img class="${imageClass}" src="${imagePath}common_book.svg" alt="">`,
    `<img class="${imageClass}" src="${imagePath}common_cake.svg" alt="">`,
    `<img class="${imageClass}" src="${imagePath}common_chicken.svg" alt="">`,
    `<img class="${imageClass}" src="${imagePath}common_cookie.svg" alt="">`,
    `<img class="${imageClass}" src="${imagePath}common_flower.svg" alt="">`,
    `<img class="${imageClass}" src="${imagePath}common_switch.svg" alt="">`,
    `<img class="${imageClass}" src="${imagePath}common_pc.svg" alt="">`,
    `<img class="${imageClass}" src="${imagePath}common_shibatan.svg" alt="">`,
    `<img class="${imageClass}" src="${imagePath}common_snowman.svg" alt="">`,
    `<img class="${imageClass}" src="${imagePath}common_surprise_box.svg">`
];

function randomSelected(array, count) {
    const copyArray = [...array];
    const randomArray = [...Array(count)].map(() => {
      const randomStartIndex = Math.floor(Math.random() * copyArray.length);
      return copyArray.splice(randomStartIndex, 1).at();
    })
    return randomArray;
}

const randomImageElements = randomSelected(imageElements, 5);
presentBoxes.forEach((presentBox, index) => {
    presentBox.insertAdjacentHTML("beforeend", randomImageElements[index]);
});

まとめ

数ヶ月後にはこのコードを忘れていそうだったので、メモとして残しておきます。

最後まで読んでいただきありがとうございました🙇‍♂️

現在こちらの書籍でJavaScript学習中です↓

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