どうも。つじけ(tsujikenzo)です。このシリーズでは 「連想配列はMapオブジェクトを使おう」 を、前半、後半でお送りします。今日は、後半戦です。
今日のアジェンダ
- 2次元配列からの変換
- dictsMapsの加工処理
- 2次元配列への変換
前回のおさらい
前回は、「連想配列はMapオブジェクトを使おう 前半」をお送りました。
今回は、実践編として、後半をお届けします。
2次元配列からの変換
GASでスプレッドシートのデータを加工するときは、まず、2次元配列で取得して、加工します。
そのさい、配列のインデックスだと、数えるのも大変ですし、列が変更されてもいいように、2次元配列を連想配列に変換するテクニックがあります。
//このような配列のままだと、インデックスで配列を操作することになってしまう
[['id', 'name', 'age'], ['tg001', 'Tsujike', 35], ['tg002', 'Etau', 38]]
//なので、このような連想配列に変換する
[{id:'tg001', name:'Tsujike', age:35}, {id:'tg002', name:'Etau', age:38}]
この、連想配列を、ディクショナリ配列と呼ぶことにします。(今まではobjectArrayと呼んでいました。)
それでは、ディクショナリを、Mapオブジェクトを使って処理してみましょう。
完成したものは、ディクショナリのMapオブジェクト配列なので、dictsMapsと、呼ぶことにします。
map()メソッドによる変換
map()メソッドのネストによる変換はこちらです。
function myFunction2_01() {
const [header, ...records] = [['id', 'name', 'age'], ['tg001', 'Tsujike', 35], ['tg002', 'Etau', 38]];
const dictsMaps = records.map(record => {
const obj = new Map();
header.map((element, index) => obj.set(element, record[index]));
return obj;
});
console.log(dictsMaps); // => [ {}, {} ]
console.log([...dictsMaps[0]]); // => [['id', 'tg001'], ['name', 'Tsujike'], ['age', 35]]
console.log([...dictsMaps[1]]); // => [['id', 'tg002'], ['name', 'Etau'], ['age', 38]]
}
reduce()メソッドによる変換
map()メソッドと、reduce()メソッドのネストによる変換はこちらです。
function myFunction2_02() {
const [header, ...records] = [['id', 'name', 'age'], ['tg001', 'Tsujike', 35], ['tg002', 'Etau', 38]];
const dictsMaps = records.map(
record => record.reduce((acc, value, index) => acc.set(header[index], value), new Map())
);
console.log(dictsMaps); // => [ {}, {} ]
console.log([...dictsMaps[0]]); // => [['id', 'tg001'], ['name', 'Tsujike'], ['age', 35]]
console.log([...dictsMaps[1]]); // => [['id', 'tg002'], ['name', 'Etau'], ['age', 38]]
}
社内コーディングガイドライン
1万行程度のデータで確認しましたが、map()メソッドタイプと、reduce()メソッドタイプの、処理速度はほとんど変わりませんでした。
なので、弊社では、reduce()メソッドタイプで統一しようと思います。
dictsMapsの加工処理
dictsMapsは、とても便利です。
配列の各要素を、インデックスではなく、header(スプレッドシートの見出し行)で指定できます。
フィルターを掛ける
const [header, ...records] = [['id', 'name', 'age'], ['tg001', 'Tsujike', 35], ['tg002', 'Etau', 38]];
const dictsMaps = records.map(
record => record.reduce((acc, value, index) => acc.set(header[index], value), new Map())
);
//37歳以上の人をフィルター掛けする
const filter = dictsMaps.filter(dictsMap => dictsMap.get('age') >= 37);
console.log(filter.length); // => 1
console.log([...filter[0]]); // => [['id','tg002'], ['name','Etau'], ['age',38]]
合計値を出す
//平均年齢を出す
const total = dictsMaps.map(dictsMap => dictsMap.get('age')).reduce((acc, cur) => acc + cur);
console.log(total / dictsMaps.length); // => 36.5 ((35 + 38) / 2)
不要な列を削除する
//不要な列を削除する
dictsMaps.forEach(dictsMap => dictsMap.delete('age'));
console.log(dictsMaps[0].size); // => 2
console.log([...dictsMaps[0]]); // => [['id','tg001'], ['name','Tsujike']]
console.log([...dictsMaps[1]]); // => [['id','tg002'], ['name','Etau']]
2次元配列への変換
スプレッドシートへsetValuesするために、dictsMapsを2次元配列に変換します。
全体を変換する
dictsMaps全体を、変換するコードはこちらです。
const [header, ...records] = [['id', 'name', 'age'], ['tg001', 'Tsujike', 35], ['tg002', 'Etau', 38]];
const dictsMaps = records.map(
record => record.reduce((acc, value, index) => acc.set(header[index], value), new Map())
);
const finalRecords = dictsMaps.map(dictMap => [...dictMap.values()]);
console.log(finalRecords); // => [['tg001','Tsujike',35], ['tg002','Etau',38]]
必要列を指定して変換する
列を選択した2次元配列を、変換するコードはこちらです。
//必要な列で構成する
const newHeaders = ['id', 'name'];
const newRecords = dictsMaps.map(dictsMap => newHeaders.map(key => dictsMap.get(key)));
console.log(newRecords); // => [ [ 'tg001', 'Tsujike' ], [ 'tg002', 'Etau' ] ]
いかがでしたでしょうか。
まとめ
以上で、「連想配列はMapオブジェクトを使おう 後半」をお送りました。
実は、連想配列をオブジェクトで操作したばあいと、Mapオブジェクトで操作したばあいは、処理速度に違いがありました。
明らかに、Mapオブジェクトの方が高速です。
弊社では、「連想配列はMapオブジェクトを使う(ただし、UrlFetch()メソッドなどのパラメーター生成時はのぞく)。」 をコーディングガイドラインとします。