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.
Related
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)
I am using the answer in the example to find the nearest object coordinate. How do I find the index of the nearest point from var points if e.g. the nearest point is {x: 12, y: 18}, then index = 1?
I want to avoid using an extra indexOf() if that step can be saved.The code is:
var points = [
{x: 10, y: 20},
{x: 12, y: 18},
{x: 20, y: 30},
{x: 5, y: 40},
{x: 100, y: 2}
];
function d(point) {
return Math.pow(point.x, 2) + Math.pow(point.y, 2);
}
var closest = points.slice(1).reduce(function(min, p) {
if (d(p) < min.d) min.point = p;
return min;
}, {point: points[0], d:d(points[0])}).point;
console.log(closest);
You could map the array to an array of objects first that also contain the index:
const points = [
{x: 10, y: 20},
{x: 12, y: 18},
{x: 20, y: 30},
{x: 5, y: 40},
{x: 100, y: 2}
];
const distance = (point) => {
return Math.pow(point.x, 2) + Math.pow(point.y, 2);
}
const result = points
.map((point, index) => ({ point, index }))
.reduce((a, b) => distance(a.point) < distance(b.point) ? a : b);
console.log(result);
I have two arrays of objects which contain a huge amount of data.
The structure of these two arrays goes something like this.
arr1 = [
{x: 1, y: '2018-01-01'},
{x: 2, y: '2018-01-02'},
{x: 3, y: '2018-01-03'},
{x: 5, y: '2018-01-05'},
....
]
arr2 = [
{x: 1, y: '2018-01-01'},
{x: 2, y: '2018-01-02'},
{x: 3, y: '2018-01-03'},
{x: 4, y: '2018-01-04'},
{x: 5, y: '2018-01-05'},
{x: 6, y: '2018-01-08'}
]
I want to update arr2 in such a way that it updates the array of objects with values that are only present in arr1 and drop any values not present in arr1. Note, I want to update the original arr2 and not return a new array.
I tried iterating through individual arrays and remove values not present but not luck.
You could get a map and iterate from the end for splicing unknown items or update changed values.
var arr1 = [{ x: 1, y: '2018-01-01x' }, { x: 2, y: '2018-01-02' }, { x: 3, y: '2018-01-03' }, { x: 5, y: '2018-01-05' }],
arr2 = [{ x: 1, y: '2018-01-01' }, { x: 2, y: '2018-01-02' }, { x: 3, y: '2018-01-03' }, { x: 4, y: '2018-01-04' }, { x: 5, y: '2018-01-05' }, { x: 6, y: '2018-01-08' }],
map = arr1.reduce((m, { x, y }) => m.set(x, y), new Map),
i = arr2.length;
while (i--) {
if (map.has(arr2[i].x)) {
if (map.get(arr2[i].x) !== arr2[i].y) {
arr2[i].y = map.get(arr2[i].x);
}
} else {
arr2.splice(i, 1);
}
}
console.log(arr2);
.as-console-wrapper { max-height: 100% !important; top: 0; }
How to filter array by comparing two arrays of objects with different elements in their objects?
I have:
arr1 =[{ x: 1, y: 2, z:3 }, { x: 2, y: 1, z:4 }];
arr2 = [{ x: 1, y: 2, a:5 }, { x: 2, y: 3, a:4 }];
I want to compare x and y values from both arrays and return the not macthing object from first array, in the above example return [{ x: 2, y: 1, z:4 }]
I tried to use _.differenceWith(arr1, arr2, _.isEqual); but obviously for this the arrays should have similar objects which is not my case.
You are very close to the right answer.
The _.differenceWith function from lodash has three arguments, the array to inspect, the values to exclude and the third argument is a comparator which determines which values you need. In your case, using _.isEqual is looking for exactly the same object (which as far as I understood is not your desired behavior).
If you only care about having same x and y values, try using your custom comparator instead of the _.isEqual function from lodash.
It would look something like this:
const arr1 = [{ x: 1, y: 2, z:3 }, { x: 2, y: 1, z:4 }];
const arr2 = [{ x: 1, y: 2, a:5 }, { x: 2, y: 3, a:4 }];
// this is your custom comparator which is called with each value from the two arrays
// I gave descriptive names to the arguments so that it is more clear
const customComparator = (valueFromFirstArray, valueFromSecondArray) =>
valueFromFirstArray.x === valueFromSecondArray.x
&& valueFromFirstArray.y === valueFromSecondArray.y;
const result = _.differenceWith(arr1, arr2, customComparator);
console.log(result);
// will print [{x: 2, y: 1, z: 4}]
Or if you are not familiar with arrow functions, the custom comparator can be declared like this:
function customComparator(valueFromFirstArray, valueFromSecondArray) {
return valueFromFirstArray.x === valueFromSecondArray.x
&& valueFromFirstArray.y === valueFromSecondArray.y
}
Here is a fiddle where you can mingle around with the custom comparator if you'd like to.
Use the filter function
arr1 =[{ x: 1, y: 2, z:3 }, { x: 2, y: 1, z:4 }];
arr2 = [{ x: 1, y: 2, a:5 }, { x: 2, y: 3, a:4 }];
let notMatched = arr2.filter(function (item, index) {
return !(item.x === arr1[index].x && item.y == arr1[index].y);
});
console.log(notMatched);
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