同じKeyの値を足し合わせたい
こちらの記事をみていました。
【JavaScript】連想配列の配列に対して、同じkeyの値を足し合わせたい - Qiita
https://qiita.com/hirakuma/items/0c4fd425f76bccab858c
const objs = [
{key1: 100, key2: 200, key3: 300},
{key1: 100, key2: 150, key3: 100},
{key1: 100, key3: 200, key4: 100},
]
sumObjects(objs)
// {key1: 300, key2: 350, key3: 600, key4: 100}
こういうデータ加工ニーズあるよな、と思い、コメント欄で自分なりの実装をしつつ、
何か工夫できないかと考えてみて、こうすればいいかと思い、複数のオブジェクトか配列をマージする関数を書きました。
const merge = (dataArray, func = v => v, target) => {
if (dataArray.length === 0) {
return target;
}
if (isUndefined(target)) {
target = new dataArray[0].constructor();
}
for (const data of dataArray) {
for (const [key, value] of Object.entries(data)) {
target[key] = func(value, target[key], key, data, target);
}
};
return target;
};
マージしたいデータをdataArrayに投入し、マージ方法をfuncに記載して、targetには何か出力結果の元となるものを指定できるものです。
target未指定の場合、dataArrayが配列内配列なら配列を生成、配列内オブジェクトならオブジェクトを生成するようにしています。
実行した結果は次の通り。様々なデータ加工ができます。
const objArray = [
{ key1: 100, key2: 200, key3: 300},
{ key1: 100, key2: 150, key3: 100},
{ key1: 100, key3: 200, key4: 100},
];
console.log(merge(objArray));
// 単純なマージ処理
// {key1: 100, key2: 150, key3: 200, key4: 100}
console.log(merge(objArray,
(v, t) => t + v,
{ key1: 0, key2: 0, key3: 0, key4: 0 },
));
// 初期値を指定した加算関数を指定すると加算したマージ
// {key1: 300, key2: 350, key3: 600, key4: 100}
console.log(merge(objArray,
(v, t) => {
return isUndefined(t) ? v : t + v;
},
));
// 初期値していなくともundefined判定をすると加算マージできる
// {key1: 300, key2: 350, key3: 600, key4: 100}
const average = merge(objArray,
(v, t) => {
return isUndefined(t) ? [1, v] : [t[0] + 1, t[1] + v];
},
);
console.log(average);
// {key1: [3, 300], key2: [2, 350], key3: [3, 600], key4: [1, 100]}
console.log(
objectFromEntries(Object.entries(average).map(
([key, value]) => [key, value[1] / value[0]]
))
);
// {key1: 100, key2: 175, key3: 200, key4: 100}
// 平均値を求めるために加算した数と値を保持している
先の記事のコメント欄にも載せました。
https://qiita.com/hirakuma/items/0c4fd425f76bccab858c#comment-568fe8aa1ef32aad00c9
配列に対しても動作する
Object.entriesは、オブジェクトだけではなく配列に対しても有効なので、オブジェクトと配列のどちらも動かす事ができます。
ほとんど同一の実装内容を npmライブラリとして公開している Parts.js に組み込みました。
テストコードを完備していますが、そこでは配列に対して、同様のマージ操作をしています。
https://github.com/standard-software/partsjs/blob/v10.0.0/source/common/_merge.js
https://github.com/standard-software/partsjs/blob/v10.0.0/source/common/common.test.js#L1550
const testArrayArray = [
[100, 200, 300],
[100, 150, 100],
[100, , 200, 100],
];
checkEqual(
[100, 150, 200, 100],
merge(testArrayArray),
);
checkEqual(
[300, 350, 600, 100],
merge(
testArrayArray,
(v, t) => t + v,
[0, 0, 0, 0],
),
);
checkEqual(
[300, 350, 600, 100],
merge(
testArrayArray,
(v, t) => isUndefined(t) ? v : t + v,
),
);
checkEqual(
[[3, 300], [2, 350], [3, 600], [1, 100]],
merge(
testArrayArray,
(v, t) => isUndefined(t) ? [1, v] : [t[0] + 1, t[1] + v],
),
);
他の場面に使えるかどうか
このmerge関数、汎用的、実用的に使えるのかな、と自分でも疑問だったので、こちらの記事をみかけたので課題にして書いてみます。
連想配列(辞書オブジェクト)n個の共通配列を上書きなしで結合マージする - Qiita
https://qiita.com/khsk/items/092fe59baee050e420b4
Javascript:オブジェクトをマージする関数mergeを考えてみた - Qiita
https://qiita.com/ttatsf/items/d5f0067f5bba8ef77433
const {
merge, isUndefined, isArray,
} = parts;
const hira = {
'dog': 'いぬ',
'cat': ['ねこ'],
'human': 'ひと',
}
const kana = {
'dog': ['イヌ'],
'cat': ['ネコ', 'ニャンコ'],
}
const kan = {
'dog': [],
'cat': '猫',
'human': '人',
'car' : '車',
}
// このような出力ができたらよい。
// [object Object] {
// car: ["車"],
// cat: ["ねこ", "ネコ", "ニャンコ", "猫"],
// dog: ["いぬ", "イヌ"],
// human: ["ひと", "人"]
// }
const result = merge([hira, kana, kan], (value, targetValue, key) => {
// 1
if (!isArray(value)) {
value = [value];
}
// 2
if (isUndefined(targetValue)) {
return value
}
// 3
for (const v of value) {
targetValue.push(v);
}
return targetValue;
});
console.log(result);
// [object Object] {
// car: ["車"],
// cat: ["ねこ", "ネコ", "ニャンコ", "猫"],
// dog: ["いぬ", "イヌ"],
// human: ["ひと", "人"]
// }
// 出力できました。
ここで動作確認しています。
https://jsbin.com/vojajepuho/edit?html,js,console
うまくうごきました。
オブジェクトのプロパティをループして関数を呼び出してきてmergeのfuncが次の引数で呼び出されます。
- value: 見ているオブジェクトのプロパティ値
- targetValue: 結果として出力されるオブジェクトのプロパティ値
- key: プロパティ名(キー)
上記のコードで、[// 1] では、
元データが配列や配列じゃないものがあるのでそれを判定し配列にして
[// 2] では
mergeの第三引数として指定されるtargetオブジェクト(未指定なら空オブジェクト生成する)のプロパティの値がtargetValueとして手に入るので、その値を見てundefinedなら、そのまま見ているオブジェクトのプロパティ値を返し
[// 3] では、targetオブジェクトのプロパティ値はすでに配列と確定しているので、そちらに見ているオブジェクトのプロパティを配列として加算する。
日本語だと説明しにくいですが、こういうコードでうまく動くようになります。
短く書いてみた。
mergeにわたす関数を短く書いてみました。
でも、読みにくいから、良い子は真似しちゃだめ。という感じです。
const result = merge([hira, kana, kan], (value, target, key) => {
value = [value].flat();
return !target ? value : [...target, ...value];
});
配列かどうか判定して配列化するのではなく、一度配列にしてflatにするというやり方と、三項演算子。また、配列へのpush はスプレッド構文で行えます。
さらに短くこんな書き方もできます。これでも同じ結果を返します。
const result = merge([hira, kana, kan],
(value, target, key) => !target ? [value].flat() : [...target, ...value]
);
こんな風に短くかけますが、現実の実務では大量のプログラムを素早く読み解いて新しい機能や修正する機能を開発しなければいけないので、素早く読めるコードがすごく大事。
なので、一つのコードブロックを読み解くのに、3秒かかるのか、1秒で済むのか、という差が開発効率に直結してきます。
プロのプログラマとしてコードを作る場合には、読みやすいコードを書いて置いたほうがいい。(そして人より3倍早く仕事する。)私はそんなふうに思っているので、短く書かずに、わざと長くわかりやすく書いたりします。
なので、参考程度です。
終わりに
merge関数というぼちぼち実用なものを作ることができました。
オブジェクトや配列を値の加工をしながらマージするときに使えると思います。
便利そうに思えたら、ダウンロードして使ってくださると嬉しいです。
@standard-software/parts - npm
https://www.npmjs.com/package/@standard-software/parts
こちらで動作確認もできるようになっています。
https://npm.runkit.com/%40standard-software%2Fparts
https://jsbin.com/johazazutu/edit?html,js,console
const { merge } = parts;
こんなふうに書くと使えます。よろしくです。