【GAS/JS】配列要素をランダム且つ平和的に抽出する

GAS
この記事は約5分で読めます。

こんにちは、SAKURUG中谷です。

GASやJavaScriptを学習していると、基礎構文の活用例として「抽選アプリを作ってみよう」というようなお題で、配列の要素をランダムに抽出するコードが紹介されていたりしますよね。

今回はその発展版として、「平和的」に抽選されるような仕組みを作ってみようと思います。

以前の記事で掃除当番システムについてご紹介しましたが、そこでも今回の仕組みが使われていたりします。

では早速いってみましょう。

平和的とは

配列要素をランダムに抽出する手段として、よく以下のパターンを見かけます。

Math.floor(Math.random() * (配列.length))

Math.floor()とMath.random()関数を組み合わせ、小数点を含む乱数を整数値で返す、というものですね。配列長を乗算することで、配列のインデックス番号がランダムに返ってくるので、その番号の要素を抽出者とする、といった要領です。

ただ、生成される乱数は無作為に決定されるので、連続して使用するような処理を記述する場合は抽出対象に偏りが発生してしまう恐れがあります。

そこで、今回は配列の要素に「抽出された回数」をカウントする数値を追加し、その回数が均等になるように抽出しようと思います。

今回使用する配列

今回はスプレッドシートを使用して配列を定義します。

候補者シート:memberInfo

スプレッドシートのデータは基本的に配列として扱われます。この場合だと下記のような二次元配列構造が取得できます。

[
  ["A", 0],
  ["B", 0],
  ["C", 1],
  ["D", 2],
  ["E", 0],
]

各要素の数値を対象が抽出される度に更新し、「各数値の最小値と一致する要素の中からランダムに抽出」すると均等になりそうです。上記の状態だと”A”, “B”, “E”が抽出の候補となるイメージです。

以下の流れで実装します。

①配列を定義する
②配列の中で、担当回数が最小値と一致する要素を探す
③一致すれば、抽出候補として配列に格納する
④③の中からランダムに抽出する
⑤④で抽出された要素を、記録用のシートに出力
⑥④で抽出された要素の担当回数を更新する

では実際のコードを見てみましょう。

コード

実装例です。

const sampling = () => {
 // ①
  const memberSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('memberInfo');
  const memberInfo = memberSheet.getRange(2, 1, 5, 2).getValues();
  const memberCount = memberSheet.getRange(2, 2, 5, 1).getValues();
  const outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('outputSheet');
  // 配列の最小値を返す変数
  const aryMin = (a, b) => Math.min(a, b);
  // 候補者を格納する配列
  const targetList = [];
  // 対象者を格納する変数
  let index, target;

  // ②③
  for (let member of memberInfo) {
    if (member[1] === memberCount.reduce(aryMin)) targetList.push(member[0]);
  }
  
  // ④
  index = Math.floor(Math.random() * targetList.length);
  target = targetList[index];
  // ⑤
  outputSheet.appendRow([new Date(), target]);
  
  // ⑥
  for (let member of memberInfo) {
    if (member[0] === target) {
      member[1] = member[1] + 1;
      memberSheet.getRange(2, 1, 5, 2).setValues(memberInfo);
    }
  }
}

⑥の処理で元の配列を更新してあげないと、いつまでも抽出候補が変わらないので注意です。
また、getRange()による範囲指定は、拡張性を考慮して本来はgetLastRow()メソッド等を活用して指定するのが望ましいかと思いますが、今回は省略しています。

では実際に挙動を確認してみましょう。

結果

試しに3回実行してみます。”A”, “B”, “E”が1回ずつ抽出されればOKとします。

実行結果:

記録用シート:outputSheet
候補者シート:memberInfo

うまく動作してそうですね。

余談

目的に応じてこの仕組みをさらに応用すれば、こんなこともできるようになります。

・抽出しない条件を追加する(googleカレンダーと連携→特定の予定がある日は対象に含めない、など)
・一度の抽出者を増やす(3人など)
・抽出者を連続させない
などなど、、、

GASやJSを学習中の方の参考になれば幸いです。

タイトルとURLをコピーしました