こんにちは、Kotaです。
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
うまく動作してそうですね。
目的に応じてこの仕組みをさらに応用すれば、こんなこともできるようになります。
などなど、、、
GASやJSを学習中の方の参考になれば幸いです。