【GAS】リストから未使用の値をランダムで取り出すツールを作成した

# はじめに

ご機嫌よう、黄瀬のん（Xenon_ura118）です。
久しぶりの投稿となります。

わたしは普段絵を描いてpixivに投稿しています。
描いたものを複数まとめて投稿しているのですが、判別のためタイトルに拡張子をつけて投稿してきます。
※鉄擬とは鉄道擬人化の略です。オリジナルの鉄道擬人化キャラを中学生頃から描き続けております…
※タイトルに拡張子をつける文化はニコニコからきているのかと思います。多分。

↓もしよければ、閲覧・フォローお願いします！
https://www.pixiv.net/users/3749039

![キャプチャ82.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3818350/d2bdad4c-8c75-81db-9fe7-36e4fa3b3f8d.png)

この表記方法をするに伴い、1つの課題が発生しました。
毎回拡張子を考えるのは面倒だなと。
（こんなことやってるのはお前しかいねーよという突っ込みはなしで！）

面倒ならば、ツール化してしまえばよいのでは？

というわけで、タイトルの通り。
拡張子リストから未使用の値を取り出すツールを作成しました。

~~この文章に見覚えがある？ｷﾉｾｲﾀﾞﾖ―~~

# スプレッドシートの内容

ツールのスプレッドは以下の通りです。

①「選出」シート
「選出」ボタンを押すと、「候補」シート（後述）の中から未使用の値をランダムで取り出します。取り出した値はA2セルに表示されます。何度でも選出可です。
「使用済みへ追加」ボタンを押すと、「使用済み」シート（後述）の最後尾に取り出した値を追加します。
![キャプチャ5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3818350/caaea69a-e999-a420-ec44-2cd9226b306b.png)

②「使用済み」シート
pixivのタイトルに使用した拡張子を積み上げています。
![キャプチャ6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3818350/65381803-b218-2d3c-1d74-f1d715723b17.png)

③「候補」シート
いろんな拡張子を記載しています。記事作成時点では330個くらいあります。
拡張子は以下のサイトを参考にしました。
https://www.tohoho-web.com/ex/extensions.html#extensions
![キャプチャ7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3818350/6d32e930-0d33-d3d7-09af-f344edee96f8.png)

# スクリプトの内容

3つのgsファイルから構成されます。

①global.gs
各メソッド内で共通する値を定義しています。

```global.gs
//スプレッドシート本体および各シートの定義
const ss = SpreadsheetApp.getActiveSpreadsheet()
const choiceSheet = ss.getSheetByName("選出")
const usedSheet = ss.getSheetByName("使用済み")
const candidateSheet = ss.getSheetByName("候補")

//候補シートの読み取り
const candidateLastRow = candidateSheet.getLastRow() //候補シートの最終行
const candidateArrayRead = candidateSheet.getRange(1, 1, candidateLastRow, 1).getValues() //候補シートから読み取った配列
const candidateArray = candidateArrayRead.flat() //読み取った配列を1次元配列へ変換（候補配列）

//使用済みシートの読み取り
const usedLastRow = usedSheet.getLastRow() //使用済みシートの最終行
const usedArrayRead = usedSheet.getRange(1, 1, usedLastRow, 1).getValues() //使用済みシートから読み取った配列
const usedArray = usedArrayRead.flat() //読み取った配列を1次元配列へ変換（使用済み配列）
```

※「global.gs」についての補足
GASでは、`sheetオブジェクト.getRange(行番号, 列番号, 行数, 列数).getValues()`で1列分のみ読み取ったとしても、2次元配列で取得されるという仕様があります。
だから1次元配列として扱いたい場合は、`読み取った配列.flat()`で変換する必要があったんですね。
（以前、この仕様に結構苦しめられた記憶があるので、同様の悩みを持った方のお役に立てれば幸いです…）

②choice.gs
候補から未使用の値を取り出すメソッドです。
「選出」ボタンに割り当てています。
```choice.gs
function choice() {
  let choicedEx //選出した拡張子
  let candidateEx //候補の拡張子
  let candidateNum //候補配列の要素番号

  let findFlag = 0 //見つかったフラグ

  let radn //乱数生成用

  //使用済みの件数が候補の件数以上の場合は、処理を中止する
  if(usedArray.length >= candidateArray.length){
    Browser.msgBox("全て使用済みであるため、処理を中止しました。")
    return
  }

  //候補が見つかるまでループ
  while(findFlag == 0){
    //乱数で要素番号を生成（0～候補の最終行-1 の範囲）
    rand = Math.random()
    candidateNum = Math.floor(rand * candidateLastRow)

    //要素番号をもとに候補の拡張子を代入
    candidateEx = candidateArray[candidateNum]

    //使用済み配列に候補の拡張子があるか検索する
    if(usedArray.indexOf(candidateEx) == -1){
      //存在しなかった場合は見つかったフラグを1に設定し、選出した拡張子として設定する
      findFlag = 1
      choicedEx = candidateEx
    }
  }

  //選出した拡張子をスプレッドシートに記載する
  choiceSheet.getRange("A2").setValue(choicedEx)

  //スプレッドシート表示更新
  SpreadsheetApp.flush()

}
```

※「choice.gs」についての補足
・`//使用済みの件数が候補の件数以上の場合は、処理を中止する`の部分は、必須ではありません。
候補にあった内容を全て使用してしまった場合、while文部分で無限ループに陥ってしまうので、安全のため入れている部分です。
まぁ、全部使い切ることはないかと思うので、入れるか入れないかについてはお任せですね。
・`//乱数で要素番号を生成（0～候補の最終行-1 の範囲）`の部分について。
`Math.random()`メソッドでは、0以上1未満の乱数を返しています。
`Math.floor(rand * candidateLastRow)`では、生成した乱数に「候補シートの最終行」の数字をかけて、floor関数で小数点以下を切り捨てています。
これで、「0～候補の最終行-1」の範囲で乱数を生成できるので、配列の要素番号として指定することができます。
・`配列.indexOf(candidateEx)`は、配列の中に引数で指定した要素が存在するかどうかを返すメソッドです。存在しない場合は-1が返されます。

③add.gs
「使用済み」シートの最後尾に取り出した値を追加するメソッドです。
「使用済みへ追加」ボタンに割り当てています。
```add.gs
function add() {
  let readEx //読み取った文字

  //選出した文字を読み取る
  readEx = choiceSheet.getRange("A2").getValue()

  //使用済みシートの最終行の1行後に追加する
  usedSheet.getRange(usedLastRow+1, 1).setValue(readEx)

  //スプレッドシート表示更新
  SpreadsheetApp.flush()
}
```

# おわりに

毎回タイトルの拡張子を考える手間が省けました（？）
今後は使用していきたいと思います。
そのためにはまずお絵かきを頑張らないとですね。

改善案等もお待ちしております。

以上です。お読みいただきありがとうございました！