見出し画像

GASでスプレッドシートの複数行のデータを扱う時の考え方 〜例えばhideRow〜

何の話か

for文でgetValueして1行ずつチマチマ処理していると時間が掛かるので、getValuesでごっそりシートの値を取ってきて、判定する値の行に対して一気にメソッドを効かせる方が速いよねっていう話をします。

自分のために、反復メソッドや、考え方、コードの書き方をきちんと理解したいので、言語化して理解を深めたく、頑張って書いてみます。

動画

前提となるシートの状態

例えば下図のようなスプレッドシートがあるとして、B列でチェックを付けているものを非表示にする、っていうのを考えてみます。

チェックがオンになっているとTRUE
チェックがOFFになっているとFALSE

データは、n行目まで、ここでは100行目まであるものとします。

シートの最大の行数はデフォルトの1000です。

1.for文で1行ずつgetValueしたものを判定

/**
 * B列 にチェックが付いている行を非表示にする
 */
function hideRow() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');
  const lastRow = sheet.getLastRow();

  // 1つずつ行を検索してif節の条件に合致する場合は非表示にする
  for (let i = 1; i <= lastRow; i++) {
    const targetCell = sheet.getRange(i, 2).getValue();
    if (targetCell === true) {
      //getRange(R1C1形式)
      const range = sheet.getRange(`${i}:${i}`);
      sheet.hideRow(range);
    }
  }
}

で、まずは素直に考えてコードを書いてみるとこうなるわけ。
これでも動くし、間違いではないわけ。
でも、これ、1行ずつ処理しているんですね。
ということはだ、これが何十行、何百行とあると、そのぶん処理に時間がかかるわけ。

このfor文では、1度目のループでは、sheet.getRange(1, 2).getValue();=B1の値を取得し、B2がtrueかどうかを判定し、trueならIf節内を実行=行を非表示する、という形になっている。

2度目のループでは、変数iが1増えて、sheet.getRange(2, 2).getValue();=B2の値を取得し以下同じとなるわけだ。

このあたりは、デバッグをするとわかりやすい。



const targetCell = sheet.getRange(i, 2).getValue(); で取れている値は何なのかを確認すると、ブーリアンのtrueであることがわかる。

検証時にi=2にした

2.配列にデータを格納し、条件合致するものを配列に格納、それを元にメソッドを効かせる(for文のパターン)

/**
 * B列 にチェックが付いている行を非表示にする
 */
function hideRow_02() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');
  const lastRow = sheet.getLastRow();

  const targetRows = [];
  const values = sheet.getDataRange().getValues();
 
  for (let row = 0; row < values.length; row++) {
    if (values[row][1] === true) targetRows.push(row + 1); // インデックス+1
  }

  // 指定の行番号をhideRows
  for (let i = 0; i < targetRows.length; i++) {
    sheet.hideRows(targetRows[i]);
  }
}

sheet.getDataRange().getValues(); でシートのデータをごっそり取ってくる。
この時、シートに余計が行や列があり、端っこの方にデータが入っていると変な配列としてデータが取得される点は懸念として持っておいた方が良いだろう。

で、console.log(values)したら、こんな感じで二次元配列が取れていることがわかる。

[ 
[ '品名', 'チェック欄' ], 
[ 'りんご', true ],
 [ 'みかん', false ],
 [ 'バナナ', true ], 
[ '品名1', true ], 
[ '品名2', false ], 
[ '品名3', true ],
以下略

配列のインデックスと、行数としての数は一つずれるので、+1をしてやってtargetRowsに格納する。
そのtargetRowsの配列に対してfor文でメソッドを効かせている。

最初の1行ずつ判定してfor文を回すよりは少ない回数のfor文にはなる、が、やはりグルグルfor文を回すという意味では、まだもたつく処理ではある。

3.forEachで一気にメソッドを効かせる

/**
 * B列 にチェックが付いている行を非表示にする
 */
function hideRow_03() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');
  const lastRow = sheet.getLastRow();

  // 隠したい行番号を格納する配列
  const targetRows = [];
  const values = sheet.getDataRange().getValues();

  values.forEach((rec, row) => {
    if (rec[1] === true) targetRows.push(row + 1); // インデックス+1
  })

  // 指定の行番号をhideRows
  targetRows.forEach(row => sheet.hideRows(row));
}

空の配列を用意するところは先ほどの2番目のコードを同じ。
const values = sheet.getDataRange().getValues();  でごっそり値を取ってくる。

で、そのごっそり取ってきたデータを使って、forEachで条件に合致するものは配列に格納していき、最後にまたforEachでそれぞれにメソッドを効かせる。


forEach((rec, row)
こういう書き方すればインデックスが取れるのはわかった。
でも何でこういう書き方ができるのかがよくわからない。
そういうもん、として飲み込めば良いのか?

いつもありがと



コールバック関数とは?forEachを改めて見てみる


改めてGAS本を見てみる。

反復メソッドは、Arrayオブジェクトの中でも特徴的なグループです。その多くは、配列内の各要素について指定の関数を呼び出すという動作をするもので、その呼び出す関数をコールバック関数といいます。

反復メソッドで使用するコールバック関数では、そのメソッドの種類に応じて、特定の役割を持つ仮引数を使用できます。たとえば、配列の各要素に処理を実行するforEachメソッドであれば、以下を格納するための3つの仮引数を使用できます。

・値
・インデックス(省略可)
・元の配列(省略可

詳解! Google Apps Script完全入門 [第3版]
高橋宣成 (著) 
P208


また、リファレンスも見てみる。



そうか、インデックスを仮引数として持てるのか。
そういうもんか(?)

こんな記事も読んだ


その代わり実際の処理が見えなくなりやすいので、コードを共有するときはノンプログラマーの壁になってしまうのだそうです。

https://yuru-wota.hateblo.jp/entry/JavaScript/CallbackFunction

わかりみ


反復メソッドを使いこなせるようになりたいぞ〜〜〜〜

#GAS
#ノンプロ研
#反復メソッド
#コールバック関数
#forEach

いただいたサポートで、書籍代や勉強費用にしたり、美味しいもの食べたりします!