[GoogleAppsScript] getValues & setValuesでGASを高速化しよう

# はじめに
GoogleAppsScript(以下、GASとよびます)では一つ一つのセルに対して値を取得(getValue)することや値を設定(setValue)することは速度的な面をみてもオススメされていません。

またGASには実行時間が5分を越えると強制終了されてしまう「[5分の壁問題](http://cannano.com/lab/program/gas/328/)」もあるわけで。

したがって、GASでは複数のセルに対して一気に値を取得・設定するメソッド、`getValues()`・`setValues()`を使用し、高速化を図りましょう。

# getValues()・setValues()

以下のようなシート構造を例に用います。
![Screen Shot 2018-07-14 at 10.51.59.png](https://qiita-image-store.s3.amazonaws.com/0/95707/e313049b-fcfa-575c-9364-af1c8640efda.png)

getValuesは以下のようにお使いいただけます。以下については一例ですので詳細に関しては[公式リファレンス](https://developers.google.com/apps-script/reference/spreadsheet/range#getvalues)を。

```js
// getValues()
var startRow    = 1,
    startColumn = 1,
    numOfRow    = 3,
    numOfColumn    = 2;
var sheet = SpreadsheetApp.getActive().getSheetByName( '<SHEET_NAME>' );
var data = sheet.getRange( startRow, startColumn, numOfRow, numOfColumn ).getValues();
```

これで以下のような結果が取得できます。

```
[
  ['1','hoge'],
  ['2','fuga'],
  ['3','bar'],
]
```

ここでは２次元配列の値が返ってくるため、**イテレーションを回す際には注意**してください。

さて、setValues()も同様にして、

```js
// setValues()
var startRow    = 1,
    startColumn = 1,
    numOfRow    = 3,
    numOfColumn    = 2;
var sheet = SpreadsheetApp.getActive().getSheetByName( '<SHEET_NAME>' );

var data = [ ['1','a'], ['2','b'], ['3','c' ] ];
sheet.getRange( startRow, startColumn, numOfRow, numOfColumn ).setValues(data);
```

以下のようになります。

![Screen Shot 2018-07-14 at 10.51.59.png](https://qiita-image-store.s3.amazonaws.com/0/95707/c0bcfd2d-aa63-6a35-fcaa-b7ccac6aae88.png)

# 「ArrayをObject[][]に変換できません。」に関して
`setValues()`の使用にあたって付いて回るエラーとして、「ArrayをObject[][]に変換できません。」というものがあります。[こちらの方の記事](https://qiita.com/ShishidoToru/items/0ab9de4ea281df9358f4)などもご参照ください。

これは単純に、setValues()の引数に２次元配列で渡すべきところをそれ以外の形（特に１次元配列など）で渡してしまうことに問題があります。

解決策としては、単純にデータ構造をよく見て、適切な２次元配列に変換してあげることです。

データ構造としては以下のようなものになるのでご参照ください。

外側の配列( **[** ['1','a'], ['2','b'], ['3','c'] **]** )の要素が**１行１行**になります。

![Screen Shot 2018-07-14 at 11.04.15.png](https://qiita-image-store.s3.amazonaws.com/0/95707/54329cbc-c578-270d-cf37-347bbe29852a.png)

内側の配列( [ **[** '1','a' **]**, **[** '2','b' **]**, **[** '3','c' **]** ] )の要素が**１列１列**になります。

![Screen Shot 2018-07-14 at 11.03.29.png](https://qiita-image-store.s3.amazonaws.com/0/95707/a537b545-4a47-63ea-5462-7af6f3bf4c3b.png)

# ２次元配列の転置
ロジックの中で、配列の転置をしたいとなった場合には以下のようなライブラリが有効です。

[gas-underscore](https://github.com/simula-innovation/gas-underscore)

## ライブラリの導入
GASではこのようなライブラリを以下のように設定して使うことができます。残念ながら`require`や`import`のようなものは現段階では使えません。

1. メニューバーの「リソース」から「ライブラリ」を選択
![Screen Shot 2018-07-14 at 10.32.43.png](https://qiita-image-store.s3.amazonaws.com/0/222368/3e97a3c9-d547-3df5-821e-456f952e92f2.png)

2. 下部の「ライブラリを追加」からuderscoreを検索し追加
![Screen Shot 2018-07-14 at 10.32.52.png](https://qiita-image-store.s3.amazonaws.com/0/222368/798087d5-4e24-c8bd-9120-8c6f22153802.png)

## gas-underscore
さて、以下のようにして転置します。

```js
  var _ = Underscore.load();
  var transData = _.zip.apply(_, data);
```

結果

```
[ 
  ['1','2','3'], 
  ['a','b','c'],
]
```

これを`setValues()`する際には転置の性質上、**RowとColumnの値が逆転します**のでご注意ください。

例えば今回の例だと以下のようにします。


```js
// setValues() 
var startRow    = 1,
    startColumn = 1,
    numOfRow    = 3,
    numOfColumn    = 2;
var sheet = SpreadsheetApp.getActive().getSheetByName( '<SHEET_NAME>' );

var data = [ ['1','a'], ['2','b'], ['3','c' ] ];
var _ = Underscore.load();
var dataTrans = _.zip.apply(_, data);

sheet.getRange( startRow, startColumn, numOfRow-1, numOfColumn+1 ).setValues(transData);
```

以下のようになります。

![Screen Shot 2018-07-14 at 11.25.04.png](https://qiita-image-store.s3.amazonaws.com/0/95707/32e18944-041d-a8f5-735f-3c7670f2a07c.png)


# 総括コード
ご参照ください。

```js
function transpose(arr) {
	var sheet = SpreadsheetApp.getActive().getSheetByName('sheet7');
	var startRow    = 1,
	    startColumn = 1,
	    numOfRow    = 3,
	    numOfColumn    = 2;

	// getValues()
	var data = sheet.getRange( startRow, startColumn, numOfRow, numOfColumn ).getValues();

	// setValues()
    sheet.getRange( startRow, startColumn, numOfRow, numOfColumn ).setValues(data);

	// Transpose data and setValues()
	var _ = Underscore.load();
	var transData = _.zip.apply(_, data);

	sheet.getRange( startRow, startColumn, numOfRow-1, numOfColumn+1 ).setValues(transData);
}

```