次の様な入出力を実現する関数dictMergeをなるべく読みやすく短い関数で実現するのが目標です。dictMergeは未定義や余分な定義を含む複数の辞書から必要なカテゴリーの語のみをまとめるための関数です。たとえなので辞書ですが今回紹介する配列、オブジェクト操作のようなやり方は色々な場面で使えます。
// 入力
const categoryKeys = ['hoge', 'fuga', 'hogefuga'];
const dictA = {
hoge : ['hoge1', 'hoge3', 'hoge5'],
fuga : ['fuga3', 'fuga7', 'fuga13'],
}
const dictB = {
hoge : ['hoge7', 'hoge9', 'hoge11'],
bar : ['bar2', 'bar4', 'bar6'],
}
const dictC = {
hoge : [],
fuga : ['fuga19', 'fuga29', 'fuga37'],
hogefuga : ['hogefuga'],
}
// 出力
const mergedDict = dictMerge(categoryKeys, dictA, dictB, dictC);
console.log(mergedDict);
/*
{
hoge : ['hoge1', 'hoge3', 'hoge5', 'hoge7', 'hoge9', 'hoge11'],
fuga : ['fuga3', 'fuga7', 'fuga13', 'fuga19', 'fuga29', 'fuga37']
hogefuga : ['hogefuga']
}
*/
実装したdictMerge()が次になります。コードの細部はコメントの通りです。
/**
* @param {Array} categoryKeys
* @param {Array} dicts
* @return {Object}
*/
function dictMerge(categoryKeys, ...dicts) { // 第二引数以下を変数dictに配列として格納する. 今回ならdicts = [dictA, dictB, dictC]
return categoryKeys.reduce((mergedDict, category) => { // reduce関数で配列を一つのオブジェクトに変換する。詳しくは下記
return {
...mergedDict, // スプレッド構文でreduceの前のループ結果を引き継がせる
[category]: dicts.map(dict => dict[category]) // [category]で変数をキー名にする。map関数で扱う配列を[dictA[category], dictB[category], dictC[category]]にする
.flat() // 配列の中身を一段flatにする。例えば[Array(3),Array(3),Array(0)]の二次元配列がArray(6)の一次元配列になる
.filter(v => v !== undefined)// 未定義でない値のみを配列の中身に残す
}
}, {});
}
上記の様にJavascriptに用意されている関数をチェーンするだけで少々複雑な問題も簡易な記述で解けます。この様なやり方を用いる際に注意する必要があるのはreduce関数です。reduce関数はJavaScriptの配列操作においてとても汎用的であり、書こうとさえ思えばほぼ全ての配列操作関数をreduce関数で表現できます。これを使うと次のように黒魔術的もかくやというコードを書けます。
// dictMergeと等価
function dictMergeByReduce(categoryKeys, ...dicts) {
return categoryKeys.reduce((mergedDict, category) => {
return {
...mergedDict,
[category]: dicts.reduce((acc, value, index) => {
acc[index] = value[category];
return acc;
}, [])
.reduce((acc, value) => acc.concat(value), [])
.reduce((acc, value) => {
if (value !== undefined) {
acc.push(value);
}
return acc;
}, [])
}
}, {});
}
なるべくreduceを使わず特化的な名前の少ない引数の配列関数を使うとわかりやすいコードになります。最初の例も正直reduceを使うまでもありません。reduce((o, k)=>{return {…o, [k]:kに関する何か}},object型引数)という構文は便利なのですが慣れない人に渡すとけっこう混乱します。注意するべきでしょう。
// 多くの人がわかりやすく読みやすいであろう版
function dictMergeSimple(categoryKeys, ...dicts) {
const mergedDict = {};
categoryKeys.forEach(category => {
mergedDict[category] = dicts.map(dict => dict[category])
.flat()
.filter(v => v !== undefined)
});
return mergedDict;
}