I need to know if an array of objects contains at least two same objects in it, in JavaScript.
I have a form that allow people to create questions (title, description, type, answer options). I need to check whether the user has entered multiple answer options with the same label. They are stored in an array.
// The array of answer options
let array = [{value: 'a'}, {value: 'b'}, {value: 'c'}, {value: 'a'}]
I tried using array.indexOf({value: 'a'}) and array.lastIndexOf({value: 'a'}) but they both give me an index of -1.
Separate objects are never === to each other, so you'll have to use a different method. One option is to create a Set of the stringified objects, and return true once any duplicate string is found:
const hasDupes = (arr) => {
const strings = new Set();
for (const obj of arr) {
const string = JSON.stringify(obj);
if (strings.has(string)) {
return true;
}
strings.add(string);
}
return false;
};
console.log(hasDupes([{value: 'a'}, {value: 'b'}, {value: 'c'}, {value: 'a'}]));
console.log(hasDupes([{value: 'a'}, {value: 'b'}, {value: 'c'}]));
If you are only concerned about the value property and you use case is not more complex and you can simply do this in one line via:
let hasDupes = arr => new Set(arr.map(x => x.value)).size !== arr.length
console.log(hasDupes([{value: 'a'}, {value: 'b'}, {value: 'c'},{value: 'a'}]))
console.log(hasDupes([{value: 'a'}, {value: 'b'}, {value: 'c'}]))
You would use Set to add the values of value and if the size of it is smaller than the actual input array length than you had duplicates. There is no need to do JSON.stringify, compare strings etc if you only care about that one property being checked.
Also JSON.stringify has issues when comparing equality of objects.
Use findIndex:
let array = [{value: 'a'}, {value: 'b'}, {value: 'c'}, {value: 'a'}];
const index = array.findIndex(({ value }) => value == "a");
console.log(index);
indexOf will return the instance of an object
{value: 'a'} !== {value: 'a'};
as they are both different instances of objects.
You can find the object
const obj = array.find(item => item.value === 'a')
You can use lodash.js to compare two objects.
Please see the sample below.
let array = [{ value: "a" }, { value: "b" }, { value: "c" }, { value: "a" }];
const object = { value: "a" };
countObjOccurrences = object => {
let occurances = 0;
array.map(item => {
if (_.isEqual(item, object)) {
occurances += 1;
}
});
return occurances;
};
This function will return 2.
Related
I have to remove all the objects from array that contain the same id present in another array.
This code works
myArray = [{id: 1, value: 'a'}, {id: 2, value: 'b'}, {id: 3, value: 'c'}];
itemsToRemove = [{id: 2, value: 'x'}, {id: 3, value: 'y'}];
for (const item of itemsToRemove) {
myArray.splice(myArray.findIndex(a => a.id === item.id), 1);
}
but I'm looking for a more elegant way to do it. I've tried also
const newArray = myArray.filter(a => itemsToRemove.findIndex(i => i.id === a.id));
but doesn't works. (it creates a new array, but at least one item with the same id remains in the new array).
Is there a clean and concise way to do it?
You're close, but as James said in the comments, findIndex is the wrong choice because it returns -1 when the item is not found. filter requires its predicate to return a boolean and -1 is not a boolean, so it's coerced to one. Unfortunately, -1 is truthy which doesn't match your intention in the predicate.
You can add a comparison to check that findIndex returned a value less than zero, or you can use find or some:
const myArray = [{id: 1, value: 'a'}, {id: 2, value: 'b'}, {id: 3, value: 'c'}];
const itemsToRemove = [{id: 2, value: 'x'}, {id: 3, value: 'y'}];
const newArray1 = myArray.filter(a => itemsToRemove.findIndex(i => i.id === a.id) < 0);
console.log(newArray1);
const newArray2 = myArray.filter(a => !itemsToRemove.find(i => i.id === a.id));
console.log(newArray2);
const newArray3 = myArray.filter(a => !itemsToRemove.some(i => i.id === a.id));
console.log(newArray3);
It's worth noting that find isn't supported in IE, nor is findIndex. some is supported by all browsers, so it's the most compatible.
some is also the most performant:
Test
Result
Operations/second
findIndex
10.63% slower
26632285
find
12.39% slower
26107649
some
fastest
29799972
This question already has answers here:
How to remove all duplicates from an array of objects?
(77 answers)
Closed 1 year ago.
Let's say I have an object
const testArray = [{key: 'a', value: 5}, {key: 'b', value: 1}, {key: 'a', value: 2}]
what I want is
newArray = [{key: 'a', value: 3}, {key: 'b', value: 1}]
What I tried is
testArray.reduce((acc, cur, index) => {
const exists = !!acc && acc.find(item => item.key === cur.key)
if (!exists){
acc.push(cur)
} else {
// can't figure out what i should do here
}
return acc;
}, [])
Or Else any other easy solution is appreciated.
Thanks
Note that when you use Array#find, the result value will keep the reference. So when you change the content it will affect the array acc.
const testArray = [{key: 'a', value: 5}, {key: 'b', value: 1}, {key: 'a', value: 2}];
const result = testArray.reduce((acc, cur) => {
const exists = acc.find(item => item.key === cur.key)
if (!exists) acc.push(cur);
else exists.value -= cur.value;
return acc;
}, []);
console.log(result);
You can take advantage of Array.prototype.reduce combine with Array.prototype.findIndex to update the result array like this
const testArray = [{key: 'a', value: 5}, {key: 'b', value: 1}, {key: 'a', value: 2}]
let result = testArray.reduce((accumulator, current, index)=>{
let itemExists = accumulator.findIndex(item => {
return item.key == current.key;
});
if(itemExists !== -1){
current = {...current, value: accumulator[itemExists].value - current.value};
accumulator[itemExists] = current;
return accumulator;
} else {
return [...accumulator,current];
}
},[])
console.log(result);
The idea is when the current item doesn't exists in the result array we just add it to the array otherwhise we update the value of the existing one by updating It value key with the existing one value key minus the value key of the current item value
You can use Map where the keys are the key property from the objects and values are the entire object.
Now for every object in the testArray check if its key is present in the Map, if it is present, then update only the value, and it is not present set the entire value.
Solution Using Map
const testArray = [{ key: "a", value: 5 }, { key: "b", value: 1 }, { key: "a", value: 2 }];
const res = Array.from(testArray.reduce(
(m, o) => (
m.has(o.key)
? m.set(o.key, { ...m.get(o.key), value: m.get(o.key).value - o.value })
: m.set(o.key, { ...o }),
m
),
new Map()
).values());
console.log(res)
Same solution but in a more readable format
const testArray = [{ key: "a", value: 5 }, { key: "b", value: 1 }, { key: "a", value: 2 }];
const res = Array.from(testArray.reduce(
(m, o) => {
if (m.has(o.key)) {
const currVal = m.get(o.key);
m.set(o.key, {...currVal, value: currVal.value - o.value})
} else {
m.set(o.key, {...o})
}
return m;
},
new Map()
).values());
console.log(res)
One Liner Using Objects
If the key is not present in the object then create an object where the value property is double the actual value.
Now for every object, just subtract the current value with the already existing value.
const
testArray = [{ key: "a", value: 5 }, { key: "b", value: 1 }, { key: "a", value: 2 }],
res = testArray.reduce((m, {key, value}) => (m[key] ??= ((value) => ({key, value}))(2*value), m[key].value -= value, m), {});
console.log(Object.values(res))
I have an array containing objects that have an identifier and the sort value. When an endpoint I'm querying returns an array with the updated objects, I need to merge it with the existing array, the incoming array's duplicated identifier objects taking precedence over the existing array but for them both to be sorted alongside each other. e.g
arrayA = [A:1, B:4, C:6]
arrayB = [D:2, A:3, C:5, G:7]
result = [D:2, A:3, B:4, C:5, G:7]
So far I can't think of any solution that doesn't include me deduping the arrays first and sorting them second, which seems wildly inefficient for the long lists that I am going to be working with. What would be the most efficient way of going about this?
You can perform the usual merge step that mergesort uses, except you can skip elements in arrayA that are already in arrayB. To perform this check quickly, you can first add all the ids from arrayB to a Set. This will result in a final runtime of O(n + m) (with extra space for the Set) instead of O((n + m) log(n + m):
function linearMerge(A, B) {
const res = [];
const newIds = new Set(B.map(o => o.id));
A = A.filter(o => !newIds.has(o.id));
let i = 0, j = 0;
while (i < A.length && j < B.length) {
if (A[i].val < B[j].val) res.push(A[i++]);
else res.push(B[j++]);
}
while (i < A.length) res.push(A[i++]);
while (j < B.length) res.push(B[j++]);
return res;
}
const arrayA = [{id: 'A', val: 1}, {id: 'B', val: 4}, {id: 'C', val: 6}];
const arrayB = [{id: 'D', val: 2}, {id: 'A', val: 3}, {id: 'C', val: 5}, {id: 'G', val: 7}];
const result = linearMerge(arrayA, arrayB);
console.log(result); // [D:2, A:3, B:4, C:5, G:7]
Of course, your original approach also works but can be improved if you use a Set to remove duplicates:
function nLogNMerge(A, B) {
const newIds = new Set(B.map(o => o.id));
return [...A.filter(o => !newIds.has(o.id)), ...B]
.sort((a, b) => a.val - b.val);
}
const arrayA = [{id: 'A', val: 1}, {id: 'B', val: 4}, {id: 'C', val: 6}];
const arrayB = [{id: 'D', val: 2}, {id: 'A', val: 3}, {id: 'C', val: 5}, {id: 'G', val: 7}];
const result = nLogNMerge(arrayA, arrayB);
console.log(result); // [D:2, A:3, B:4, C:5, G:7]
I tested both approaches where arrayA and arrayB have 4500 entries, and the linear merge indeed outperforms the second approach (about "20%" faster). You can find the test here: https://jsperf.com/merge-sorted-arrays-with-duplicates/1
I have an array of objects like:
var a = [
{id: 1, name: 'A'},
{id: 2, name: 'B'},
{id: 3, name: 'C'},
{id: 4, name: 'D'}
];
And Ids array which i want to remove from array a :
var removeItem = [1,2];
I want to remove objects from array a by matching its ids, which removeItem array contains. How can i implement with lodash.
I Checked lodash's _.remove method, but this need a specific condition to remove an item from array. But i have list of ids which i want to remove.
As you mentioned you need the _.remove method and the specific condition you mention is whether the removeItem array contains the id of the checked element of the array.
var removeElements = _.remove(a, obj => removeItem.includes(obj.id));
// you only need to assign the result if you want to do something with the removed elements.
// the a variable now holds the remaining array
You have to pass a predicate function to .remove method from lodash.
var final = _.remove(a, obj => removeItem.indexOf(obj.id) > -1);
using indexOf method.
The indexOf() method returns the first index at which a given element
can be found in the array, or -1 if it is not present.
You can do it using native javascript using filter method which accepts as parameter a callback function.
var a = [
{id: 1, name: 'A'},
{id: 2, name: 'B'},
{id: 3, name: 'C'},
{id: 4, name: 'D'}
];
var removeItem = [1,2];
a = a.filter(function(item){
return removeItem.indexOf( item.id ) == -1;
});
console.log(a);
But filter method just creates a new array by applying a callback function.
From documentation:
The filter() method creates a new array with all elements that pass
the test implemented by the provided function.
If you want to modify the original array use splice method.
var a = [
{id: 1, name: 'A'},
{id: 2, name: 'B'},
{id: 3, name: 'C'},
{id: 4, name: 'D'}
];
var removeItem = [1,2];
removeItem.forEach(function(id){
var itemIndex = a.findIndex(i => i.id == id);
a.splice(itemIndex,1);
});
console.log(a);
Compare Array of Objects
function compare (arr1, arr2){
//if object key value pair from arr2 exists in arr1 return modified array
for (let obj of arr2) {
if(obj.key === arr1.key){
return obj
}
}
}
// Should return [{key: 1, name : "Bob", {key: 2, name : "Bill"}]
compare([{key: 1}, {key: 2}],
[{key: 1, name : "Bob"}, {key: 3, name : "Joe"}, {key: 2, name : "Bill"}])
I am having a disconnect with looping arrays of objects with different lengths and properties. I have tried looping and IndexOf but due to different lengths, I cannot compare the two arrays that way. I feel like a filter might be a good tool but have had no luck. Any thoughts?
Create a Set of properties from the 1st array (the keys), and then Array#filter the 2nd array (the values) using the set:
function compareBy(prop, keys, values) {
const propsSet = new Set(keys.map((o) => o[prop]));
return values.filter((o) => propsSet.has(o[prop]));
}
const result = compareBy('key', [{key: 1}, {key: 2}],
[{key: 1, name : "Bob"}, {key: 3, name : "Joe"}, {key: 2, name : "Bill"}])
console.log(result);