Lodash/JS orderBy with nested null values - javascript

My question is kinda linked to this one, but one step more complex : lodash orderby with null and real values not ordering correctly
Basically I have something like this :
let objs = [{x: {y: 1, z: 2}, a: 3}, {x: null, a: 4}];
let sortKeys = ['x.y', 'a']; //this is dynamic, can have multiple fields
The user can pass different values for sorting, like x.y or just a and sometimes x is null. So when I use the normal _.orderBy(objs, sortKeys, ['desc']) it shows null values first.
How can I do so that the null values appears last?

Pure ES6 solution would look like this:
let objs = [{x: {y: 1, z: 2}, a: 3}, {x: null, a: 4}];
objs.sort(({x: x1}, {x: x2}) => {
const y1 = x1 === null ? Infinity : x1.y;
const y2 = x2 === null ? Infinity : x2.y;
return y1 - y2;
});
console.log(objs);
null's are simply weighted at a maximum possible value and sorted accordingly.

This solution partitions the array to values that are null or undefined (unsortable) via _.isNil(). It then sorts the sortable items, and concats the unsortable to the end:
const { partition, get, isNil, orderBy } = _;
const fn = (arr, key, order) => {
const [unsortable, sortable] = partition(arr, o => isNil(get(o, key)));
return orderBy(sortable, key, order)
.concat(unsortable);
}
const objs = [{x: {y: 1, z: 2}, a: 3}, {x: {y: 4, z: 2}, a: 3}, {x: null, a: 4}];
const result = fn(objs, 'x.y', 'desc');
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>

Related

javascript Object.assign() without overriding fields

Is there any way to merge two objects, like Object.assign(a, b), but I want the same field in a keeps its origin value (without overriding from b).
a = {x: 1, y: 2}
b = {y: 3, z: 4}
Object.assign(a, b)
// Now we get a = {x: 1, y: 3, z: 4}, so what if I want {x: 1, y: 2, z: 4}?
console.log(a)
Note: thanks for the efforts from the answers, the key requirements for the question is:
modify a
not too much code
not too slow
To modify the a reference (like you seem to be wanting to do from your example), you could do:
const a = {x: 1, y: 2};
const b = {y: 3, z: 4};
Object.assign(a, {...b, ...a});
console.log(a);
This essentially says, replace the overlapping properties in b with those from a, and then merge this replaced object into a.
Above, the {...b, ...a} first merges a with b, so a overwrites properties in b, giving us:
{y: 3, z: 4, x: 1, y: 2}
// evalutes to
{z: 4, x: 1, y: 2}
Now we merge this result into a with the Object.assign() call:
{x: 1, y: 2, z: 4, x: 1, y: 2}
// evalutes to
{z: 4, x: 1, y: 2}
// ie:
{x: 1, y: 2, z: 4}
Edit:
To meet your requirements, use a regular for...in loop, it's efficient, doesn't require much code (especially if you remove the blocks), and modifies a:
const a = {x: 1, y: 2};
const b = {y: 3, z: 4};
for(const key in b) a[key] ??= b[key];
console.log(a); // {"x": 1, "y": 2, "z": 4}
The above works if your values won't be nullish (null/undefined) as it uses logical nullish assignment (??=), otherwise, you can replace the assignment with:
a[key] = key in a ? a[key] : b[key];
The best solution to this is to avoid using Object.assign and use spread operator as by doing so you'll achieve your goal with simple logic. In spread operator, the rightmost element overwrites the left one.
a = {x: 1, y: 2};
b = {y: 3, z: 4};
result = {...b, ...a};
result2 = {...a, ...b};
console.log(result); // {x: 1, y: 2, z: 4}
console.log(result2); // {x: 1, y: 3, z: 4}
//if you don't want to create new object and just modify a then
Object.assign(a, {...b, ...a});
console.log(a); // {x: 1, y: 2, z: 4}
let a = { x: 1, y: 2 };
let temp = { ...a };
let b = { y: 3, z: 4 };
Object.assign(a, b);
Object.assign(a, temp);
console.log(a);
You could use Object.entries and filter out keys that are already in a.
eg.
a = {x: 1, y: 2}
b = {y: 3, z: 4}
//Object.assign(a, b)
// Now we get a = {x: 1, y: 3, z: 4}, so what if I want {x: 1, y: 2, z: 4}?
Object.assign(a, Object.fromEntries(
Object.entries(b).
filter(([k])=>!(k in a))));
console.log(a)
Finally I found the perfect solution is lodash.defaults.
https://lodash.com/docs/4.17.15#defaults
import _ from 'lodash'
a = {x: 1, y: 2}
b = {y: 3, z: 4}
_.defaults(a, b)
// Outputs {x:1, y:2, z:4}, perfectly as expected.
console.log(a)

Find object within array of arrays and return the array it belongs to

I have the following array structure:
const array = [array1, array2, array3];
Each one of the three arrays consists of objects of form:
array1 = [{x: 0, y: 1}, {x: 5, y: 9}, {x: 1, y: 8}, {x: 3, y: 2}, etc]
I am trying to find the most efficient way to go through arrays of array and return the array to which a particular (unique) object belongs. For example I have object
{x:9, y:5}
which can be uniquely found in array2, so I want to return array2.
here's what I've tried:
const array = [array1, array2, array3];
for (let x = 0; x < array.length; x++) {
for (let y = 0; y < array[x].length; y++) {
array[x].find(e => e === array[x][y])
return array[x];
}
}
You'll need two loops, but you can use methods that do the iteration for you:
let array1 = [{x: 0, y: 1}, {x: 5, y: 9}, {x: 1, y: 8}, {x: 3, y: 2}];
let array2 = [{x: 5, y: 4}, {x: 4, y: 5}, {x: 8, y: 8}, {x: 3, y: 2}];
let array3 = [{x: 4, y: 3}, {x: 0, y: 6}, {x: 7, y: 8}, {x: 5, y: 2}];
const array = [array1, array2, array3];
let obj = array2[2]; // let's find this one...
let result = array.find(arr => arr.includes(obj));
console.log(result);
Here use find
data = [
[{x:1, y:2}, {x:2, y:3}],
[{x:3, y:2}, {x:4, y:3}],
[{x:5, y:2}, {x:6, y:3}],
[{x:7, y:2}, {x:8, y:3}]
];
const getArray = ({x, y}) => data.find(a => a.some(o => o.x === x && o.y === y));
console.log(getArray({x:3, y:2}));
TLDR; There is a working example in this fiddle
This can be accomplished using the following 3 things:
a library such as lodash to check for object equality (https://lodash.com/docs/4.17.15#isEqual)
The reason for this is that the behaviour of directly comparing two objects is different than you might think more info here
array.findIndex to find the index of the outer array (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex)
array.find to find the element in an inner array (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
The following method findObjectInNestedArray will do what you'd like.
const findObjectArray = (obj, arr) => {
index = arr.findIndex(a => a.find(e => _.isEqual(e, obj)))
return arr[index] // will return `undefined` if not found
}
// Example code below
const array1 = [{x: 0, y: 1}, {x: 5, y: 9}, {x: 1, y: 8}, {x: 3, y: 2}];
const array2 = [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3, y: 3}, {x: 4, y: 4}, {x:9, y:5}];
const array3 = [{x: 5, y: 5}];
const arrays = [array1, array2, array3];
const inArray2 = {x:9, y:5};
const notInAnyArray = {x:0, y:0};
console.log('array2', findObjectArray(inArray2, arrays));
console.log('not in array', findObjectArray(notInAnyArray, arrays));
I know I said a single iteration was impossible before, but I devised a possible method that could work under specific circumstances. Essentially, you can sort the properties then stringify the objects for instant lookups. The sort is necessary to ensure you always get a consistent stringified output regardless of the object's properties preexisting order. There are three caveats to this method:
the objects CANNOT contain functions. Properties with functions are dropped in the stringification process.
NaN and infinity are converted to null, which can cause unexpected "matches" in the cache
If the depth of the object is not known (i.e. the target objects can contain references to arrays and other objects), then you'll need to deeply traverse through every level before stringifying.
It's a trade-off that's only improves performance when comparing deeply nested or extremely large objects. It's scalable, though, I guess.. Here's an example of how it could be done:
// sort's an array's values, handling subarrays and objects with recursion
const sortArr = arr => arr.sort().map(el => typeof el === 'object' ? (Array.isArray(el) ? sortArr(el) : sortObj(el)) : el)
// sorts a key's objects, then recreates the object in a consistent order
const sortObj = obj => Object.keys(obj).sort().reduce((final, prop) => {
final[prop] = (
// if it's an object, we'll need to sort that...
typeof obj[prop] === 'object'
? (
Array.isArray(obj[prop])
? sortArr(obj[prop])//<-- recursively sort subarray
: sortObj(obj[prop])//<-- recursively sort subobject
)
// otherwise, just retrun the value
: obj[prop]
)
return final
}, {})
// for every element in the given array, deeply sort then stringify it
const deepSortObjectArray = (arr) => arr.map(el => JSON.stringify(sortObj(el)))
// from those strings, create an object with the strings as values and an associated 'true' boolean
const obejctCache = (obj) => deepSortObjectArray(obj).reduce((acc, el) => ({[el]: true, ...acc}), {})
// create an object string cache for every object in the array:
const cacheObjectArrays = arr => arr.map(obj => obejctCache(obj))
// perform an O(1) lookup in each of the caches for a matching value:
const findArrayContainer = (obj, caches) => {
const stringLookupObj = JSON.stringify(sortObj(obj))
return caches.findIndex(cache => cache[stringLookupObj])
}
const array = [
{y: 1, x: 0},
{x: 5, y: 9},
{x: 1, y: 8},
{x: 3, y: {z: 3, x: 1, y: 2}}
]
const arrayArray = [[], [], array]
const cachesArrays = cacheObjectArrays(arrayArray)
console.log(cachesArrays)
/* output: [
{},
{},
{ '{"x":3,"y":{"x":1,"y":2,"z":3}}': true,'{"x":1, "y":8}': true, '{"x":5,"y":9}': true,'{"x":0,"y":1}': true }
]
*/
console.log(findArrayContainer({y: 1, x: 0}, cachesArrays))
// output: 2; working normally!
console.log(findArrayContainer({x: 0, y: 1}, cachesArrays))
// output: 2; working regardless of order!
console.log(findArrayContainer({y: 1, x: 0, q: 0}, cachesArrays))
// output: -1; working as expected with non-found objects!
As you can see, it's pretty complicated. Unless you're 100% this is actually the performance bottleneck, these performance gains may not translate to making interaction smoother.
Let me know if you have any questions about it!

Is there another way to solve this forEach problem using map, filter, reduce...?

If i have two arrays of objects like these two
const x = [{id: 1, a: 5}, {id: 2, a:10}, {id: 3, a: 12}];
const y = [{id: 4, a: 0}, {id: 2, a: 0}, {id: 3, a: 0}];
The output should be a new array represents y
but with some modifications, if an item in y has an id matches an item in x, a value should be the same as x, so the output should be
[{id: 4, a: 0}, {id: 2, a: 10}, {id: 3, a: 12}]
This is my solution
const z = [...y];
z.forEach(el => x.map(ele => el.a = el.id === ele.id ? ele.a : el.a));
This is a simple implementation of what i am doing in a project, i care about performance and i see the step of cloning the array z = [...y] may be expensive, so i am looking for a solution using functions that return a new array map, filter, reduce...,
I tried nested map and filter, find ... but i ended with complex solutions, so are there another solutions that would be more performant and simple in same time?
Instead of iterating over x for each value of y you can create a xObj from x by single iteration and in the loop over y just check if it's exist in the xObj (constant lookup O(1) ) if yes then update it otherwise use the existing value.
const x = [{id: 1, a: 5}, {id: 2, a:10}, {id: 3, a: 12}];
const y = [{id: 4, a: 0}, {id: 2, a: 0}, {id: 3, a: 0}];
var xObj = {};
x.forEach(function(val){
xObj[val.id] = val.a;
});
const newY = y.reduce(function(o,i){
if(xObj.hasOwnProperty(i.id)){
i.a = xObj[i.id];
}
o.push(i);
return o;
},[]);
console.log(newY);
Use Set to store the ids available in x array and use map over y array to check whether y's id exists in the Set or not.
const x = [{id: 1, a: 5}, {id: 2, a:10}, {id: 3, a: 12}];
const y = [{id: 4, a: 0}, {id: 2, a: 0}, {id: 3, a: 0}];
const mapped = Object.values(x).reduce((acc, {id}) => {
acc.add(id)
return acc;
}, new Set());
const result = y.map((obj, index) => mapped.has(obj.id) ? {...obj, a: x[index].a} : obj);
console.log(result);
Hope this will help!
For a problem like this you should care far less about the computational expense of creating a new array, and far more about the Big O expense of running through multiple arrays multiple times. Both your solution and the other one posted by #NitishNarang appear to be O(n^2) because as your arrays become larger, the number of steps required to solve increases exponentially.
Personally I would simply create a new Map() and go through each item one by one, adding it to the Map() only if the value of a corresponding to that ID is larger than the currently stored one. It's basically a slightly more complicated sorting exercise except with unique ID values thrown in.
const x = [{id: 1, a: 5}, {id: 2, a:10}, {id: 3, a: 12}];
const y = [{id: 4, a: 0}, {id: 2, a: 0}, {id: 3, a: 0}];
const myMap = new Map();
for (const ea of x) {
if (!myMap.has(ea.id) || ea.a >= myMap.get(ea.id).a) {
myMap.set(ea.id, ea);
}
}
for (const ea of y) {
if (!myMap.has(ea.id) || ea.a >= myMap.get(ea.id).a) {
myMap.set(ea.id, ea);
}
}
const result = [...myMap.values()];
This solution is O(n), i.e. it's linear, meaning that if you add 10 or 100 or 1000 more items to the array x or y or both, it will only add that many more steps to running the solution instead of 10^2 or 100^2 or 1000^2 because you have to check every single item in each array against every other item in the other array (like you do with your original solution.)
Edit: as #SZenC pointed out, the above solution is not quite correct as it combines both arrays. In order to selectively match items only if they originally exist in array y, simply iterate over y first, and then only replace values when iterating over x if they are already present in the map:
const x = [{id: 1, a: 5}, {id: 2, a:10}, {id: 3, a: 12}];
const y = [{id: 4, a: 0}, {id: 2, a: 0}, {id: 3, a: 0}];
const myMap = new Map();
for (const ea of y) {
if (!myMap.has(ea.id) || ea.a >= myMap.get(ea.id).a) {
myMap.set(ea.id, ea);
}
}
for (const ea of x) {
if (myMap.has(ea.id) && ea.a >= myMap.get(ea.id).a) {
myMap.set(ea.id, ea);
}
}
const result = [...myMap.values()];
Please try this. It uses "map" and "find"
const x = [{id: 1, a: 5}, {id: 2, a:10}, {id: 3, a: 12}];
const y = [{id: 4, a: 0}, {id: 2, a: 0}, {id: 3, a: 0}];
const result = y.map(yData => (xResult = x.find(xData => xData.id == yData.id), xResult && { ...yData, a: xResult.a } || yData))
console.log(result)

Iterating over an array of objects, summing values with the same index, and returning a new array of objects

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 }

Best way to get intersection of keys of two objects?

I have two object literals like so:
var firstObject =
{
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30
}
var secondObject =
{
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30
}
I want to get the intersection of the keys these two object literals have like so:
var intersectionKeys = ['x', 'y', 'z', 'a']
I can obviously do a loop and see if a key with the same name exists in the other object, but I am wondering if this would be a good case for some functional programming and map / filter / reduce usage? I myself have not done that much functional programming, but I have a feeling, that there could exist a clean and clever solution for this problem.
A solution without indexOf.
var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };
function intersection(o1, o2) {
return Object.keys(o1).concat(Object.keys(o2)).sort().reduce(function (r, a, i, aa) {
if (i && aa[i - 1] === a) {
r.push(a);
}
return r;
}, []);
}
document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');
Second attempt with O(n).
var firstObject = { x: 0, y: 1, z: 2, a: 10, b: 20, e: 30 },
secondObject = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 };
function intersection(o1, o2) {
return Object.keys(o1).filter({}.hasOwnProperty.bind(o2));
}
document.write('<pre>' + JSON.stringify(intersection(firstObject, secondObject), 0, 4) + '</pre>');
The given answers are nice and astonishing but there could be a problem in void's answer and that is:
"What if one of property values intentionally set to undefined."
Nina's answer is good (really fantastic) but as we are in era of fun JavaScript I think mine wont be too bad:
var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30 }
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30 }
function intersect(o1, o2){
return Object.keys(o1).filter(k => Object.hasOwn(o2, k))
}
document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');
Update
onalbi mentioned some performance issue in comments which is rational and therefore the code bellow seems to be a better way to handle the problem:
var a = { x: undefined, y: 1, z: 2, a: 10, b: 20, e: 30};
var b = { x: 0, y: 1, z: 2, a: 10, c: 20, d: 30};
function intersect(o1, o2) {
const [k1, k2] = [Object.keys(o1), Object.keys(o2)];
const [first, next] = k1.length > k2.length ? [k2, o1] : [k1, o2];
return first.filter(k => k in next);
}
document.write('<pre>' + JSON.stringify(intersect(a, b)) + '</pre>');
The procedure i will suggest is:
Get the array of keys using Object.keys() for one of the objects.
Find the intersection the array using .filter and checking if the second object contains a key matching the first array.
var firstObject = {
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30
}
var secondObject = {
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30
}
function getIntKeys(obj1, obj2){
var k1 = Object.keys(obj1);
return k1.filter(function(x){
return obj2[x] !== undefined;
});
}
alert(getIntKeys(firstObject, secondObject));
Recursive function
This is other solution, maybe help you. I used a recursive function to intercept two objects. The advantage of this solution is that you not need worry about attributes that are objects at same time.
In this case the function intercept attributes that exist in both objects and asign the value of 'objSource' like final value of attribute intercepeted.
{
function interceptObjects(objSource, objInterface) {
let newObj = {};
for (const key in objSource) {
if (objInterface.hasOwnProperty(key)) {
// in javascript an array is a object too.
if (objSource[key] instanceof Object && !Array.isArray(objSource[key]) && objInterface[key] instanceof Object && !Array.isArray(objInterface[key])) {
newObj[key] = {};
newObj[key] = interceptObjects(objSource[key], objInterface[key])
} else {
newObj[key] = objSource[key];
}
}
}
return newObj;
}
// FOR TESTING
let objSource = {
attr1: '',
attr2: 2,
attr3: [],
attr4: {
attr41: 'lol',
attr42: 12,
attr43: 15,
attr45: [1, 4],
},
attr5: [2, 3, 4],
};
let objInterface = {
attr1: null,
attr4: {
attr41: null,
attr42: 12,
attr45: [1],
},
attr5: [],
attr6: null,
};
console.log(this.interceptObjects(objSource, objInterface));
}
Here is a simple entry, very functional, handles any number of objects, and returns the values of the matching keys from the first object passed.
This behavior is similar to that of array_intersect_key() in PHP in case anyone is searching for that.
function intersectKeys(first, ...rest) {
const restKeys = rest.map(o => Object.keys(o));
return Object.fromEntries(Object.entries(first).filter(entry => restKeys.every(rk => rk.includes(entry[0]))));
}
Expanded here for better explanation and commenting
function intersectKeys(first, ...rest) {
// extract the keys of the other objects first so that won't be done again for each check
const restKeys = rest.map(o => Object.keys(o));
// In my version I am returning the first objects values under the intersect keys
return Object.fromEntries(
// extract [key, value] sets for each key and filter them, Object.fromEntries() reverses this back into an object of the remaining fields after the filter
Object.entries(first).filter(
// make sure each of the other object key sets includes the current key, or filter it out
entry => restKeys.every(
rk => rk.includes(entry[0])
)
)
);
// to get JUST the keys as OP requested the second line would simplify down to this
return Object.keys(first).filter(key => restKeys.every(rk => rk.includes(key)));
}
It's important to note that this solution only works on string keys, Symbol keys will be ignored and the final object will not contain any. Though a similar function could be written to compare Symbol intersect as well.
I know this is an old post, however, I want to share a solution I wrote today that I believe is efficient and clean.
function intersectingKeys(...objects) {
return objects
.map((object) => Object.keys(object))
.sort((a, b) => a.length - b.length)
.reduce((a, b) => a.filter((key) => b.includes(key)));
}
This function can take in n number of objects, and find the intersecting keys.
This is how it works.
Map the objects, creating an array of key arrays.
Sort the array by length, this puts the smallest key arrays first.
Finally, reduce our key arrays, by filtering each list of keys against the next list.
I think the clever part of this algorithm is the pre sorting of the key arrays. By starting with the smallest list of keys, we have less work to do comparing keys.
Here is the usuage:
var firstObject = {
x: 0,
y: 1,
z: 2,
a: 10,
b: 20,
e: 30,
};
var secondObject = {
x: 0,
y: 1,
z: 2,
a: 10,
c: 20,
d: 30,
};
intersectingKeys(firstObject, secondObject);
// [ 'x', 'y', 'z', 'a' ]

Categories

Resources