I've 2 array of objects that I'd deeply compare with lodash
However, I've a prob with it:
> var x = [{a:1, b:2}, {c:3, d:4}];
> var y = [{b:2, a:1}, {d:4, c:3}];
> _.difference(x,y, _.isEqual);
[ { a: 1, b: 2 }, { c: 3, d: 4 } ]
How should I compare to see that both are equal?
You can make use of differenceWith() with an isEqual() comparator, and invoke isEmpty to check if they are equal or not.
var isArrayEqual = function(x, y) {
return _(x).differenceWith(y, _.isEqual).isEmpty();
};
var result1 = isArrayEqual(
[{a:1, b:2}, {c:3, d:4}],
[{b:2, a:1}, {d:4, c:3}]
);
var result2 = isArrayEqual(
[{a:1, b:2, c: 1}, {c:3, d:4}],
[{b:2, a:1}, {d:4, c:3}]
);
document.write([
'<div><label>result1: ', result1, '</label></div>',
'<div><label>result2: ', result2, '</label></div>',
].join(''));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.11.2/lodash.js"></script>
UPDATE June 22, 2018
This update is in response to the comment below:
#ryeballar if any of the array is undefined it returns true. How would
you solve this. Thanks in advance buddy
As stated in the differenceWith documentation:
The order and references of result values are determined by the first
array.
This means that as long as all the items in the first array will match everything else in the second array, then the resulting array from the differenceWith invocation will be empty.
An alternative solution that truly solves the problem is to use xorWith() with the same chain of functions from the solution above.
var isArrayEqual = function(x, y) {
return _(x).xorWith(y, _.isEqual).isEmpty();
};
var result1 = isArrayEqual(
[{a:1, b:2}, {c:3, d:4}],
[{b:2, a:1}, {d:4, c:3}]
);
var result2 = isArrayEqual(
[{a:1, b:2, c: 1}, {c:3, d:4}],
[{b:2, a:1}, {d:4, c:3}]
);
var result3 = isArrayEqual(
[{a:1, b:2, c: 1}, {c:3, d:4}],
[{b:2, a:1}, {d:4, c:3}, undefined]
);
console.log('result1:', result1);
console.log('result2:', result2);
console.log('result3:', result3);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Following #ryeballar answer, if you only want to import specific lodash methods you can use this notation:
import { isEmpty, isEqual, xorWith } from 'lodash';
export const isArrayEqual = (x, y) => isEmpty(xorWith(x, y, isEqual));
Both answers on above with xorWith & differenceWith do not take in count if the array has different length only if the current item is found in the second array.
var isArrayEqual = function(x, y) {
return _(x).xorWith(y, _.isEqual).isEmpty();
};
var result = isArrayEqual(
[{a:1, b:2}],
[{a:1, b:2}, {a:1, b:2}]
);
console.log('result should be false:', result);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
In that particular case, we also would have to compare both arrays length.
const isArrayEqual = function(x, y) {
const isSameSize = _.size(x) === _.size(y);
return isSameSize && _(x).xorWith(y, _.isEqual).isEmpty();
};
const result = isArrayEqual(
[{a:1, b:2}],
[{a:1, b:2}, {a:1, b:2}]
);
console.log('result should be false:', result);
.as-console-wrapper{min-height:100%;top:0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
I prefer pure JS since i haven't got the patience to learn underscore or lodash. So i invent something i have been long dreaming of. The Object.prototype.compare(). The v0.0.2 is doing only shallow comparison though but adequate for this question.
Object.prototype.compare = function(o){
var ok = Object.keys(this);
return typeof o === "object" && ok.length === Object.keys(o).length ? ok.every(k => this[k] === o[k]) : false;
};
var obj1 = {a:1,b:2,c:3},
obj2 = {c:3,a:1,b:2},
obj3 = {b:2,c:3,a:7};
document.write ("<pre>" + obj1.compare(obj2) + "</pre>\n");
document.write ("<pre>" + obj2.compare(obj3) + "</pre>\n");
document.write ("<pre>" + new Object({a:1, b:2, c:3}).compare({c:3,b:2,a:1,d:0}) + "</pre>\n");
Cool... So then lets continue with the question... I guess... since we already have an Object.prototype.compare() there should be absolutely no harm in the invention of Array.prototype.compare(). Lets make it more clever this time. It shall tell primitives from objects. One other thing is, arrays are ordered; so in my book [1,2] is not equal to [2,1]. Also this makes the job simpler.
Object.prototype.compare = function(o){
var ok = Object.keys(this);
return typeof o === "object" && ok.length === Object.keys(o).length ? ok.every(k => this[k] === o[k]) : false;
};
Array.prototype.compare = function(a){
return this.every((e,i) => typeof a[i] === "object" ? a[i].compare(e) : a[i] === e);
}
var x = [{a:1, b:2}, {c:3, d:4}],
y = [{b:2, a:1}, {d:4, c:3}],
a = [1,2,3,4,5],
b = [1,2,3,4,5],
p = "fourtytwo",
r = "thirtyseven",
n = 42,
m = 37;
document.writeln(x.compare(y)); // the question is answered here
document.writeln(a.compare(b));
document.writeln(p.compare(r)); // these primitives end up at Object prototype
document.writeln(n.compare(m)); // so modify Object.prototype.compare () accordingly
Related
I have an array of objects like :
a = [{"a":1,"b":2},{"a":3,"c":5}]
I would like to obtain, in an efficient way :
b = [{"a":1,"b":2,"c":"blabla"},{"a":3,"b":"blabla","c":5}]
My function so far is (using the underscoreJS library in the first line):
let fillFuncArr = (res,fill) => {
// we fill the potential missing values
let col_names = _.uniq(res.map(x => Object.keys(x)).reduce((acc, curr) => acc.concat(curr), []), false);
for (let i = 0; i < res.length; i++) {
for (let j = 0; j < col_names.length; j++) {
if (!res[i].hasOwnProperty(col_names[j])) {
res[i][col_names[j]] = fill;
}
}
}
return res;
};
In my example above you would do :
b = fillFuncArr(a,"blabla")
How to make that function faster if at all possible ?
===========================================================================
EDIT after answers to benchmark replies :
I tested the functions like this :
for (let i=0;i<num_tries;i++) {
let time_tmp = performance.now();
console.time("old");
fillFuncArr(obj,"blah");
console.timeEnd("old");
time_old.push(performance.now()-time_tmp);
time_tmp = performance.now();
console.time("new");
fillFuncArrNew(obj,"blah");
console.timeEnd("new");
time_new.push(performance.now()-time_tmp);
}
This answer (the first calculation of the old function is always much faster than the subsequent ones, not quite sure why...) is 50-100 times faster. The fill time is the same, it's getting the keys that makes up all the speed gains :
"old": [
147.52006196975708,
1065.4309248924255,
1023.5124139785767,
1021.830512046814,
1855.5670911073685,
1006.7114781141281,
996.8541929721832,
1306.3085260391235
],
"new": [
18.814231991767883,
23.46549105644226,
17.708116054534912,
15.55942702293396,
18.764864921569824,
15.866382002830505,
19.18179702758789,
23.987511038780212
]
Don't know if this is faster, but definitely shorter:
dummy = {a: 'dummy', b: 'dummy', c: 'dummy'}
a = [{"a": 1, "b": 2}, {"a": 3, "c": 5}]
r = a.map(x => ({...dummy, ...x}))
console.log(r)
If you want the dummy to be fully dynamic, then
function fillKeys(a, value) {
let keys = new Set(a.flatMap(Object.keys)),
dummy = Object.fromEntries(
[...keys].map(k => [k, value]));
return a.map(x => ({...dummy, ...x}));
}
//
a = [{"a": 1, "b": 2}, {"a": 3, "c": 5}]
r = fillKeys(a, 'dummy')
console.log(r)
That being said, I'm sure this is actually an XY problem, so it would help to explain us what you're actually doing. For example, if you just want all objects in the list to respond to the same set of keys, that would be much easier (and also faster) with proxies.
First figure out all keys in the array items, create a "default" object filled with the fill as its values, then iterate over the items and spread the default object, then the original object:
const fillFuncArr = (arr, fill) => {
const allKeys = new Set(arr.flatMap(Object.keys));
const defaultObj = {};
for (const key of allKeys) {
defaultObj[key] = fill;
}
return arr.map(obj => ({ ...defaultObj, ...obj }));
};
a = [{"a":1,"b":2},{"a":3,"c":5}]
b = fillFuncArr(a,"blabla")
console.log(b);
Take a look here:
console.time('allTest');
var was = [{a:1, b:2}, {a:3, c:5}];
function blah(array){
var a = [], o, b = 'blabla';
array.forEach(function(w){
o = {};
o.a = 'a' in w ? w.a : b;
o.b = 'b' in w ? w.b : b;
o.c = 'c' in w ? w.c : b;
a.push(o);
});
return a;
}
console.time('test'); console.log(blah(was)); console.timeEnd('test'); console.timeEnd('allTest');
At least this will show you how to test the time.
const a = [{"a":1,"b":2},{"a":3,"c":5}];
const allKeys=[...new Set(a.flatMap(Object.keys))]
const dummyObj=Object.fromEntries(allKeys.map(key => [key, 'blabla']));
console.log(a.map(data => ({...dummyObj, ...data})))
Im trying to figure out whats the best way to get an intersection object between two objects using es6.
by this i mean something like:
a = {a:'a',b:'b',c:'c', d:'d'};
b = {a:'a',b: '1', c:'c', d:'2', f'!!!'}
// result I want:
c = getDifference(a,b)
//c is now: {b:'1', d:'2'}
Is there a short way to do this using es6, or do I need to iterate over the a object using for(in) with Object.keys() and compare, assigning intersections to c?
(a,b) => {
const c = {};
for(const _key in Object.keys(a)){
if(b[_key] && b[_key] !== a[_key]){
c[_key] = b[_key];
}
}
return c;
}
I know loadash/underscore has these kinds of helper functions... but trying to see if es6 has any new short syntax for this, and if not whats the shortest way to do this using vanilla js.
You can get the entries of object b using Object.entries() and then filter out the key-value pairs which are the same as those in a using .filter(), then, you can rebuild your object using Object.fromEntries() like so:
const a = {a:'a',b:'b',c:'c', d:'d'};
const b = {a:'a',b: '1', c:'c', d:'2', f:'!!!'}
const getDifference = (a, b) =>
Object.fromEntries(Object.entries(b).filter(([key, val]) => key in a && a[key] !== val));
// result I want:
const c = getDifference(a,b); // peforms b-a
console.log(c); // {b:'1', d:'2'}
If you can't support Object.fromEntries(), then you can use .reduce() instead to build the object for you:
const a = {a:'a',b:'b',c:'c', d:'d'};
const b = {a:'a',b: '1', c:'c', d:'2', f:'!!!'}
const getDifference = (a, b) =>
Object.entries(b).filter(([key, val]) => a[key] !== val && key in a).reduce((a, [key, v]) => ({...a, [key]: v}), {});
// result I want:
const c = getDifference(a,b); // peforms b-a
console.log(c); // {b:'1', d:'2'}
Use reduce for more concise code, but your approach was the clearest:
const getDifference = (a, b) => Object.entries(a).reduce((ac, [k, v]) => b[k] && b[k] !== v ? (ac[k] = b[k], ac) : ac, {});
We use Object.entries to avoid getting the value if we used Object.keys - it's just easier. Since b[k] may not exist, we use the short-circuit logical AND && operator - so if a key from a doesn't exist in b, it's not added to the result object. Then we check if the two values are equal - if they are, then nothing needs to be added, but if they're not, we add the key and value from b to the result object. In both cases, we return the result object.
You can achieve this in a single short with Object.keys();
const mainObj = {
a: 'a', b: 'b', c: 'c', d: 'd',
};
const comapareObj = {
a: 'a', b: '1', c: 'c', d: '2', f: '!!!',
};
const findVariantsElement = (main, compareWith) => {
const result = {};
Object.keys(main).forEach((r) => {
const element = main[r];
if (compareWith[r]) {
if (element !== compareWith[r]) {
result[r] = compareWith[r];
}
}
});
return result;
};
const c = findVariantsElement(mainObj, comapareObj);
console.log(c);
You can use .map() also for the same
const mainObj = {
a: 'a', b: 'b', c: 'c', d: 'd',
};
const comapareObj = {
a: 'a', b: '1', c: 'c', d: '2', f: '!!!',
};
const findVariantsElement = (main, compareWith) => {
const result = {};
Object.keys(main).map((r) => {
const element = main[r];
if (compareWith[r]) {
if (element !== compareWith[r]) {
result[r] = compareWith[r];
}
}
});
return result;
};
const c = findVariantsElement(mainObj, comapareObj);
console.log(c);
var x = [{a:1, b:4,c:5}, {a:1, b:2,c:7}];
var y = [{a:1, b:2,c:6}, {a:1, b:2,c:8}];
I want to compare based on first 2 key i.e a,b and get the index if it is unequal. In above example output should be fetched as 0 since b value is nt equal. How do we achieve in javascript or Lodash ? Thank you.
So you want a compare function which will compare an array of object in which you want index of an object whose two properties a and b are not equal.
Below implementation return such index(0-base) if exit else return -1.
function compare(x,y){
for(let i=0;i<x.length && i<y.length;i++){
if(x[i].a!=y[i].a || x[i].b!=y[i].b)
return i;
}
return -1;
}
var x = [{a:1, b:4,c:5}, {a:1, b:2,c:7}];
var y = [{a:1, b:2,c:6}, {a:1, b:2,c:8}];
console.log(compare(x,y)); //0
y = [{a:1, b:4,c:6}, {a:1, b:2,c:8}];
console.log(compare(x,y));//-1
y = [{a:1, b:4,c:6}, {a:1, b:3,c:8}];
console.log(compare(x,y));//1
Hope this you want.
Assuming you want to compare two objects in an array by first two keys, here's a working example. Please, feel free to elaborate.
var x = [{a:1, b:4,c:5},{a:1, b:2,c:7}];
var y = [{a:1, b:2,c:6}, {a:1, b:2,c:8}];
function customizer(obj1, obj2) {
const [a, b] = Object.keys(obj1).slice(0, 2);
return obj1[a] === obj2[a] && obj1[b] === obj2[b];
}
xIsEqual = _.isEqualWith(x[0], x[1], customizer);
yIsEqual = _.isEqualWith(y[0], y[1], customizer);
console.log(xIsEqual); // false
console.log(yIsEqual); // true
If you want to filter an array on a specific condition, you can use filter() :
y.filter(d => d.a !== d.b);
With Lodash:
_.filter(y, d => d.a !== d.b);
You can't rely on an order of object keys, 'cause iteration over object keys is implementation-dependent. It could differ from browser to browser.
Read this note.
For example, run the following code:
const obj1 = { a: 'a', 100: 'b', 2: 'c' };
console.log('object literal', Object.keys(obj1));
// at least in chrome it would be ["2", "100", "a"]
const obj2 = {};
obj2['a'] = 'a';
obj2['100'] = 'b';
obj2['2'] = 'b';
console.log('object with keys added in a specific order', Object.keys(obj2));
// same ["2", "100", "a"]
Here is what I have, I've tried to do it in a for in loop and it's not quite doing what I need it to do. I thought of doing $.each, but I wasn't sure how to get past just looping the array.
var myKeys = [{a:1, b:10}, {a:5, b:8}, {a:3, b:2}, {a:1, b:6}];
I want to run a function that searches thru the keys. Finds the lowest 'b' value, and then returns the 'a' key. In the case above: search and find 2, but return 3 ( {a:3, b:2} )
Tried this, but I just get returned 0, 1, 2, 3 (all the keys) and their index.
for (var key in myKeys) {
if (myKeys.hasOwnProperty(myKeys[key]) <= 2) {
console.log(key);
}
}
Any insight on how to fix it would be great. If it were just the 1 value, I wouldn't have a problem. It's that there are multiple and I need to return the other value.
var myKeys = [{a:1, b:10}, {a:5, b:8}, {a:3, b:2}, {a:1, b:6}];
var min = myKeys.reduce(function(current, previous){
return (current.b > -1 && current.b < previous.b ? current : previous);
}, {a:-1, b:-1});
console.log(min);
Somehting like this?
var myKeys = [{a:1, b:10}, {a:5, b:8}, {a:3, b:2}, {a:1, b:6}];
for (var x = 0; x < myKeys.length; x++) {
var this_key = myKeys[x];
for (var key in this_key) {
if (this_key[key] <= 2) {
console.log(key);
}
}
};
I want to run a function that searches thru the keys. Finds the lowest
'b' value, and then returns the 'a' key. In the case above: search and
find 2, but return 3
You can create variables index, n to store index of element having "b" property of object and value of "b" property. Use for..of loop to iterate Array.prototype.entries() of array. Check if current object of array "b" property is less than previous object "b" property. Use index following for..of loop to get "a" property of myKeys[index].
var myKeys = [{a:1, b:10}, {a:5, b:8}, {a:3, b:2}, {a:1, b:6}];
let n = index = 0;
for (let [key, prop, curr] of myKeys.entries()) {
if (({b:curr} = prop, curr) < n || !n) [n, index] = [curr, key];
}
let {a} = myKeys[index]; // call `delete n` here if `n`:`2` not needed;
console.log({a});
Demo Object:
var foo = {a:1, b:2, c:3, d:4, e:5, f:6, g:7}
Wanted result: (get top 3 keys by value)
{e:5, f:6, g:7}
Explanation:
For a given key/value basic object, how would you get the 3 top values, but not just the values but also the keys? keys could be anything. lets say values are integers.
performance should be in mind.
You can extract the properties into an array, then sort the array:
var foo = {a:1, b:2, c:3, d:4, e:5, f:6, g:7}
var props = Object.keys(foo).map(function(key) {
return { key: key, value: this[key] };
}, foo);
props.sort(function(p1, p2) { return p2.value - p1.value; });
var topThree = props.slice(0, 3);
If you want the result as an object, just reduce it back to one
var topThreeObj = props.slice(0, 3).reduce(function(obj, prop) {
obj[prop.key] = prop.value;
return obj;
}, {});
Get key/value pairs:
let pairs = Object.entries(foo);
Sort them:
pairs.sort((a, b) => a[1] - b[1]);
Turn some back into an object:
let result = Object.fromEntries(pairs.slice(-3));