Is it possible in some way to filter let's say such an array of object arrays:
[[{id: 1}, {id: 2}, {id: 3}], [{id:6}, {id: 2}], [{id: 2}, {id: 1}, {id: 9}, {id: 3}]]
To get array of objects which all arrays have the same property (id), so in this case it output will be:
[{id: 2}] // becasue this id is the same in all three subarrays
I've only try intersectionBy from loadash but it seems to work in completely other way :/
I would take one array (it's enough to take one because if the property is not there its already not common), in this example I'm taking the first one but probably more efficient will be picking the shortest one.
iterate over the array and check for each object if its common to all other arrays.
const arr = [[{id: 1}, {id: 2}, {id: 3}], [{id:6}, {id: 2}], [{id: 2}, {id: 1}, {id: 9}, {id: 3}]];
let firstArray = arr.shift();
const result = firstArray.reduce((common, item)=>{
if (arr.every(inner => inner.some(_item => _item.id === item.id))) {
common.push(item);
}
return common;
},[])
console.log(result);
Using Ramda:
const input = [[{id: 1}, {id: 2}, {id: 3}], [{id:6}, {id: 2}], [{id: 2}, {id: 1}, {id: 9}, {id: 3}]];
R.intersection(...input);
You can use array reduce,forEach , findIndex and sort to get the most common object. In first inside the reduce callback use forEach to iterate each of the child array and then use findIndex to find if in accumulator array , there exist an object with same id. If it does not exist create a new object with key id & occurrence. If it exist then increase the value of occurrence. This will give the most common id, even if an id is not present in few child array
let data = [
[{id: 1}, {id: 2}, { id: 3}],
[{id: 6}, {id: 2}],
[{id: 2}, {id: 1}, {id: 9}, { id: 3}]
];
let obj = data.reduce((acc, curr) => {
curr.forEach((item) => {
let getInd = acc.findIndex((elem) => {
return elem.id === item.id
});
if (getInd === -1) {
acc.push({
id: item.id,
occurence: 1
})
} else {
acc[getInd].occurence += 1;
}
})
return acc;
}, []).sort((a, b) => {
return b.occurence - a.occurence;
});
console.log(obj[0])
var arr = [
[{id: 1}, {id: 2}, {id: 3}],
[{id:6}, {id: 2}],
[{id: 2}, {id: 1}, {id: 9}, {id: 3}]
]
var obj = {};
var arrLength = arr.length;
arr.forEach((val,index) => {
val.forEach((item) =>{
if(index == 0){
if(!obj.hasOwnProperty(item.id)){
obj[item.id] = 1;
}
}else{
if(obj.hasOwnProperty(item.id)){
obj[item.id] = obj[item.id] + 1;
}else{
return;
}
}
});
});
var output = [];
for (const property in obj) {
if(obj[property] == arrLength){
output.push({
id: property
})
}
}
console.log(output);
My approach is similar to that of naortor, but with an attempt to be more generic.
const intersection = (pred) => (as, bs) =>
as .filter (a => bs .some (b => pred (a, b)))
const intersectionAll = (pred) => (xs) =>
xs.length ? xs .reduce (intersection (pred)) : []
const input = [[{id: 1}, {id: 2}, {id: 3}], [{id:6}, {id: 2}], [{id: 2}, {id: 1}, {id: 9}, {id: 3}]]
const eqIds = (a, b) => a .id == b .id
console .log (
intersectionAll (eqIds) (input)
)
.as-console-wrapper {min-height: 100% !important}
This version requires you to say how you identify two equal values. (We will check that they have the same id, but any binary predicate function is allowed.) This function is passed to intersection which returns a function that takes two arrays and finds all the element in common between those two. intersectionAll wraps this behavior up, folding intersection over an array of arrays.
This breakdown is useful, as intersection is a useful function on its own too. And abstracting out the id check into a function you need to supply means these functions are much more generic.
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 salaryDataand 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?
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; }
Explanation
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.
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; }
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);
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!
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);
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);
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 ') );
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 }
I have an array of objects containing num and sum
[
{num: 1, sum: 10},
{num: 1, sum: 2},
{num: 1, sum: 3},
{num: 2, sum: 1},
{num: 2, sum: 40},
{num: 2, sum: 100},
{num: 3, sum: 0},
{num: 4, sum: 0},
]
How I want is:
[
{1: {count: 3, totalSum: 15},
{2: {count: 3, totalSum: 141},
{3: {count: 1, totalSum: 0},
{4: {count: 1, totalSum: 0},
]
Any help is highly appreciated
An option is to reduce into an object indexed by nums, and then map that object into an array:
const input=[{num:1,sum:10},{num:1,sum:2},{num:1,sum:3},{num:2,sum:1},{num:2,sum:40},{num:2,sum:100},{num:3,sum:0},{num:4,sum:0},]
const outputObj = input.reduce((a, { num, sum }) => {
if (!a[num]) a[num] = { count: 0, totalSum: 0 };
a[num].count++;
a[num].totalSum += sum;
return a;
}, {});
const output = Object.entries(outputObj)
.map(([key, val]) => ({ [key] : val }));
console.log(output);
You can use reduce method in order to group the array and then use another reduce method in order to find count and totalSum properties for every key.
let arr = [ {num: 1, sum: 10}, {num: 1, sum: 2}, {num: 1, sum: 3}, {num: 2, sum: 1}, {num: 2, sum: 40}, {num: 2, sum: 100}, {num: 3, sum: 0}, {num: 4, sum: 0}, ]
var groupBy = function(arr, key) {
return arr.reduce(function(acc, x) {
(acc[x[key]] = acc[x[key]] || []).push(x);
return acc;
}, {});
};
let grouped = groupBy(arr, 'num');
let result = Object.keys(grouped).reduce(function(acc, key){
let obj ={ [key] : {count : grouped[key].length, totalSum: grouped[key].map(a => a.sum).reduce((a,b) => a + b)}}
acc.push(obj)
return acc;
},[]);
console.log(result);