Reputation: 7822
I have an array of objects, something like this:
const data = [ // array1
[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}]
],[ // array2
[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}]
]
What needs to be accomplished is summing x
from the array1
with x
from the array2
that have the same index. Same goes for y
and z
. The final result should be a new array of objects containing the summed values.
Something like this:
[
[{totalXOne: 2}, {totalYOne: 4}, {totalZOne: 6}],
[{totalXTwo: 2}, {totalYTwo: 4}, {totalZTwo: 6}],
[{totalXThree: 2}, {totalYthree: 4}, {totalZThree: 6}],
]
Note: All arrays are the same length, and if a value is missing it will be replaced with 0
)
I found something nice on MDN, but it's summing all x
, y
, z
values, and it's returning single summed values, like this:
let initialValue = 0;
let sum = [{x: 1}, {x:2}, {x:3}].reduce(function(accumulator,currentValue) {
return accumulator + currentValue.x;
}, initialValue)
Output:
[
[{totalX: 3}, {totalY: 6}, {totalZ: 9}], // this is not what I need
]
Is there any way I can achieve this?
UPDATE
I'm receiving JSON
from another source. It contains a property called allEmpsData
mapping over it I get the necessary salaryData
and mapping over it I'm getting the NET|GROSS|TAX data.
let allReports = [];
setTimeout(() => {
allEmpsData.map(x => {
let reports = {};
let years = [];
let months = [];
let netArr = [];
let grossArr = [];
let mealArr = [];
let taxArr = [];
let handSalaryArr = [];
x.salaryData.map(y => {
years.push(y.year);
months.push(y.month);
netArr.push(y.totalNetSalary);
grossArr.push(y.bankGrossSalary);
mealArr.push(y.bankHotMeal);
taxArr.push(y.bankContributes);
handSalaryArr.push(y.handSalary);
})
reports.year = years;
reports.month = months;
reports.net = netArr;
reports.gross = grossArr;
reports.meal = mealArr;
reports.taxesData = taxArr;
reports.handSalaryData = handSalaryArr;
allReports.push(Object.assign([], reports));
});
}, 1000);
As I can tell, everything is working as it should, but the truth is,. I don't know any better. Then here goes the magic:
setTimeout(() => {
result = allReports.reduce((r, a) =>
a.map((b, i) =>
b.map((o, j) =>
Object.assign(...Object
.entries(o)
.map(([k, v]) => ({ [k]: v + (getV(r, [i, j, k]) || 0) }))
)
)
),
undefined
);
console.log(result);
}, 1500);
... and it returns an empty array in the node console, but if I console.log
any other property from the updated code above, it's there. Any suggestions?
Upvotes: 6
Views: 384
Reputation: 546
This solution returns a single object with each key's value being added up.
const arr1 = [
[{x: 1}, {y: 2}, {z: 3}],
[{x: 4}, {y: 6}, {z: null}],
[{x: 5}, {y: 7}, {z: 9}]
]
const arr2 = [
[{x: 12}, {y: 20}, {z: 4}],
[{x: 13}, {y: 21}, {z: 3}],
[{x: 14}, {y: 22}, {z: 5}]
]
const arr3 = [
[{x: 2}, {y: 10}, {z: 67}],
[{x: 3}, {y: 31}, {z: 23}],
[{x: null}, {y: 25}, {z: null}]
]
function get_keys (arr) {
let keys = []
for (let i = 0; i < arr[0].length; i++) {
let key = Object.keys(arr[0][i])[0]
keys.push(key)
}
return keys
}
function sum_by_key (arrays) {
let res = {}
let keys = get_keys(arrays)
let all_obj = []
for (let i = 0; i < arrays.length; i++) {
for (let d = 0; d < arrays[i].length; d++) {
all_obj.push(arrays[i][d])
}
}
for (let i = 0; i < keys.length; i++) {
let k = keys[i]
res[k] = 0
for (let d = 0; d < all_obj.length; d++) {
let __k = Object.keys(all_obj[d])[0]
if (k === __k) {
res[k] += all_obj[d][__k]
}
}
}
return res
}
let arrays = [...arr1, ...arr2, ...arr3]
console.log(sum_by_key(arrays)) //=> { x: 54, y: 144, z: 114 }
Upvotes: 0
Reputation: 22876
A bit shorter alternative:
var data = [ [ [{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}] ],
[ [{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}] ] ];
var result = data.reduce( (a, b) => a.map((_, i) =>
Array.from('xyz', (k, j) => [ { [k]: a[i][j][k] + b[i][j][k] } ] ) ) );
console.log( JSON.stringify( result ).replace(/]],/g, ']],\n ') );
Upvotes: 1
Reputation: 26161
What you ask is basically known as zipWith
function. So a generic solution could be laid as;
var data = [[[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}]],
[[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}]]],
zipWith = (a,b,f) => a.map((e,i) => f(e,b[i])),
zipper = (sa,sb) => sa.map((o,i) => Object.keys(o)
.reduce((r,k) => (r[k] = o[k] + sb[i][k], r), {})),
result = data.reduce((p,c) => zipWith(p,c,zipper));
console.log(result);
Upvotes: 1
Reputation: 386560
You could use a helper function for getting a value of a nested object and map the values at the same index.
const getV = (o, p) => p.reduce((t, k) => (t || {})[k], o);
var data = [[[{ x: 1 }, { y: 2 }, { z: 3 }], [{ x: 1 }, { y: 2 }, { z: 3 }], [{ x: 1 }, { y: 2 }, { z: 3 }]], [[{ x: 1 }, { y: 2 }, { z: 3 }], [{ x: 1 }, { y: 2 }, { z: 3 }], [{ x: 1 }, { y: 2 }, { z: 3 }]]],
result = data.reduce((r, a) =>
a.map((b, i) =>
b.map((o, j) =>
Object.assign(...Object
.entries(o)
.map(([k, v]) => ({ [k]: v + (getV(r, [i, j, k]) || 0) }))
)
)
),
undefined
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 3
Reputation: 350147
Here is a functional programming way to do it, using an intermediate ES6 Map
:
const data = [[[{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}]], [[{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}],[{x: 1}, {y:2}, {z:3}]]];
const result = data[0].map( (arr, i) => Array.from(data.reduce( (acc, grp) => (
grp[i].forEach( o =>
Object.entries(o).forEach( ([k, v]) => acc.set(k, (acc.get(k) || 0) + v))
), acc
), new Map), ([k, v]) => ({ [k]: v })) );
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
To facilitate the explanation let's agree on some terms:
We have the input (an array), consisting of groups. Each group is an array consisting of rows. Each row consists of objects, each having one property/value pair.
The output does not have the group level, but it has the rows, again consisting of objects, each having one property/value pair.
So using these terms let's go through the code:
As the number of rows in the output array is equal to the number of rows in any of the groups, it seems a good start to map the rows of the first group, i.e. like data[0].map
.
For each row in the output, we need to make sums, and reduce
is a good candidate function for that job, so we call data.reduce
. For the initial value of that reduce
call I have passed an empty Map
. The purpose is to fill that Map with key-sum pairs. Later we can then decompose that Map into separate objects, each having one of those key/sum pairs only (but that is for later).
So the reduce
starts with a Map
and iterates over the groups. We need to take the ith row from each group to find the objects that must be "added". So we take the row grp[i]
.
Of each object in that row we get both the property name and value with Object.entries(o)
. In fact that function returns an array, so we iterate over it with forEach
knowing that we will actually only iterate once, as there is only one property there in practice. Now we have the key (k
) and value v
. We're at the deepest level in the input structure. Here we adjust the accumulator.
With acc.get(k)
we can know what we already accumulated for a particular key (e.g. for "x"). If we had nothing there yet, it gets initialised with 0 by doing || 0
. Then we add the current value v
to it and store that sum back into the Map with acc.set(k, ....)
. Using the comma operator we return that acc
back to the reduce
implementation (we could have used return
here, but comma operator is more concise).
And so the Map gets all the sums per key. With Array.from
we can iterate each of those key/sum pairs and, using the callback argument, turn that pair into a proper little object (with { [k]: v }
). The [k]
notation is also a novelty in ES6 -- it allows for dynamic key names in object literals.
So... Array.from
returns an array of little objects, each having a sum. That array represents one row to be output. The map
method creates all of the rows needed in the output.
Upvotes: 4
Reputation: 919
Try this simple and small code snipet:
const data = [ // array1
[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}]
],[ // array2
[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}],
[{x: 1}, {y:2}, {z:3}]
]
var array1 = data[0];
var array2 = data[1];
var returnArray = [];
array1.forEach(function (subArray1, index){
var subArray2 = array2[index];
var subReturn = [];
subArray1.forEach(function (obj, i) {
var variableVal;
if (i == 0){variableVal = "x";} else if (i == 1) {variableVal = "y";}
else if (i == 2) {variableVal = "z"}
var newObj = {};
newObj[variableVal] = obj[variableVal] + subArray2[i][variableVal];
subReturn[i] = newObj;
});
returnArray[index] = subReturn;
});
console.log(returnArray);
Upvotes: 1
Reputation: 8239
Try the following:
var arr1 = [[{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}]];
var arr2 = [[{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}], [{x: 1}, {y:2}, {z:3}]];
var map = {
0 : 'x',
1 : 'y',
2 : 'z'
};
var map2 = {
0 :"One",
1 :"Two",
2 : "Three"
};
var result = [];
var obj= {};
for(var i = 0; i < arr1.length; i++){
total = 0;
var arr =[];
for(var j =0; j < arr1[i].length; j++){
obj["total"+ map[j] + map2[i]] = arr1[i][j][map[j]] + arr2[i][j][map[j]];
arr.push(obj);
obj = {};
}
result.push(arr);
}
console.log(result);
Upvotes: 2
Reputation: 5188
It's a good idea to try and break this sort of problems down into smaller problems, and build up gradually. This means we don't have to look at the whole thing in one go.
Let's write a function that adds together individual elements from an array:
function addElements(element1, element2, key, rowIndex) {
//for now we keep the keys the same, otherwise multiple additions
//won't work
return {
[key]: element1[key] + element2[key]
};
}
Now let's add two rows together, using our addElements()
:
function addRows(row1, row2, rowIndex) {
return ['x', 'y', 'z'].map((key, index) => {
// "key" will go through "x", "y", and "z" as
// "index" goes 0, 1, 2
const element1 = row1[index];
const element2 = row2[index];
return addElements(element1, element2, key, rowIndex);
});
}
Now we can iterate through all the rows in our first matrix, and add the equivalent from the second matrix using addRows()
:
function addMatrices(matrix1, matrix2) {
return matrix1.map((row1, index) => {
const row2 = matrix2[index];
return addRows(row1, row2, index);
});
}
Now we can turn this into a reducer:
const EMPTY_MATRIX = { ... }; //define a matrix of all zeroes here
matrices.reduce(addMatrices, EMPTY_MATRIX);
Hope this helps!
Upvotes: 1