javascript Object.assign() without overriding fields - javascript

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)

Related

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!

Lodash/JS orderBy with nested null values

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>

Nesting d3.max with array of arrays

I have an array of arrays like so.
data = [
[
{x: 1, y: 40},
{x: 2, y: 43},
{x: 3, y: 12},
{x: 4, y: 60},
{x: 5, y: 63},
{x: 6, y: 23}
], [
{x: 1, y: 12},
{x: 2, y: 5},
{x: 3, y: 23},
{x: 4, y: 18},
{x: 5, y: 73},
{x: 6, y: 27}
], [
{x: 1, y: 60},
{x: 2, y: 49},
{x: 3, y: 16},
{x: 4, y: 20},
{x: 5, y: 92},
{x: 6, y: 20}
]
];
I can find the maximum y value of data with a nested d3.max() call:
d3.max(data, function(d) {
return d3.max(d, function(d) {
return d.y;
});
});
I'm struggling to understand how this code actually works. I know the second argument of the d3.max() function specifies an accessor function - but I'm confused into how exactly calling d3.max() twice relates with the accessor function.
I guess what I'm asking for is a walkthrough of how javascript interprets this code. I've walked through it on the console but it didn't help unfortunately.
Sometimes it's all about the naming of the variables:
// the outer function iterates over the outer array
// which we can think of as an array of rows
d3.max(data, function(row) {
// while the inner function iterates over the inner
// array, which we can think of as an array containing
// the columns of a single row. Sometimes also called
// a (table) cell.
return d3.max(row, function(column) {
return column.y;
});
});
You can find the source code for the d3.max function here: https://github.com/d3/d3.github.com/blob/8f6ca19c42251ec27031376ba9168f23b9546de4/d3.v3.js#L69
Wow..! intriguing question really. Just for some sporting purposes here is an ES6 resolution of this problem by invention of an array method called Array.prototype.maxByKey() So here you can see how in fact it's implemented by pure JS.
Array.prototype.maxByKey = function(k) {
var m = this.reduce((m,o,i) => o[k] > m[1] ? [i,o[k]] : m ,[0,Number.MIN_VALUE]);
return this[m[0]];
};
var data = [
[{x: 1, y: 40},{x: 2, y: 43},{x: 3, y: 12},{x: 4, y: 60},{x: 5, y: 63},{x: 6, y: 23}],
[{x: 1, y: 12},{x: 2, y: 5},{x: 3, y: 23},{x: 4, y: 18},{x: 5, y: 73},{x: 6, y: 27}],
[{x: 1, y: 60},{x: 2, y: 49},{x: 3, y: 16},{x: 4, y: 20},{x: 5, y: 92},{x: 6, y: 20}]
],
maxObj = data.map(a => a.maxByKey("y")).maxByKey("y");
console.log(maxObj);
Here is the story of what's going on in this piece of code. We will find the index of the object by reducing. Our reduce method uses an initial value, which is array [0,Number.MIN_VALUE], which at index 0 has 0 and at index 1 position has the smallest possible number in JS. Initial values are set to the first argument. So here m starts with the initial value. Reduce will walk over the array items (objects in our case) one by one and each time o will be assigned to the current object and the last argument i is of course the index of the position we are currently working on. k is provided to our function as the key that we will be using to test the max value upon.
So there is this simple ternary comparison o[k] > m[1] ? [i,o[k]] : m which means check current object property given by k (o[k]) if it is less than m[1] (where m is [0,Number.MIN_VALUE] in the first turn) return m as [i,o[k]] (check how ternaries return result) if it is not less than m[1] then return m as it is. And at the end of the walk we will be reduced down to [index of the element with max k property value, the value of that k property] in that array.
So as you see it is very simple.

Convert array of objects into array of primitives (extracted from object properties)

Consider the following code:
var input = [{x: 1, y: 6}, {x: 4, y: 3}, {x: 9, y: 2}];
var output = convert(input);
console.log(output); // = [1, 6, 4, 3, 9, 2]
What is the shortest, most concise convert function I can write that will give me the output shown?
So far I've come up with the following:
function convert(input) {
var output = [];
input.forEach(function(obj) {
output.push(obj.x, obj.y);
});
return output;
}
But surely there's a nice one-liner way of doing this?
With Array.prototype.reduce method it will save you two lines of code:
function convert(arr) {
return arr.reduce(function(prev, curr) {
return prev.concat(curr.x, curr.y);
}, []);
}
var input = [{x: 1, y: 6}, {x: 4, y: 3}, {x: 9, y: 2}];
document.write(JSON.stringify( convert(input) ));

Remove duplicates from object array - Underscore

Trying to get _.uniq() working on the following structure:
[
{'x' : 1, 'y': 2},
{'x' : 1, 'y': 2},
{'x' : 2, 'y': 3},
{'x' : 2, 'y': 4},
{'x' : 3, 'y': 4}
]
where the result should be:
[
{'x' : 1, 'y': 2},
{'x' : 2, 'y': 3},
{'x' : 2, 'y': 4},
{'x' : 3, 'y': 4}
]
ie duplicate items removed. I'd like to avoid stringify, because I then just have to parse each back out to a JSON object.
Any help would be appreciated.
EDIT: tring Matt's solution below, I'm missing something I think - this doesn't work. If I log the values for a and b, I see
_.uniq($scope.validNotes, function (a, b) {
console.log(a, b);
return a.x === b.x && a.y === b.y;
});
Object {x: 2, y: 3} 0
Object {x: 1, y: 0} 1
Object {x: 2, y: 3} 2
Object {x: 3, y: 2} 3
Object {x: 4, y: 2} 4
Object {x: 5, y: 1} 5
Which obviously means I'll never find any dupes
Because two objects are not == or ===, just because they have the same keys, you will need to use the [iterator] argument of the uniq() function;
_.uniq(myArray, function (item) {
return item.x + item.y;
});
Note you'll need to return a unique string combination; consider the case of { x: 11, y: 1 } and { x: 1, y: 11 }; my code will resolve both to 111, and treat them as the same.
Something like:
_.uniq(myArray, function (item) {
return 'x:' + item.x + 'y:' + item.y;
});
... would be more resilient, but it depends on the values of your data, of course.
If you have abstract data structure in your JSON, it will be better to compare array elements one bt one like strings, using stringify. Because you have new object in every array element, every literal object notation ie {} creates new object in javascript language and it's doesn't metter has it the same properties or not.
But, if you have stucture like x,y use Matt answer

Categories

Resources