JavaScript Object Array: Removing objects with duplicate properties - javascript

I have an array of objects:
[
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
I'd like to strip out objects with duplicate Ids, leaving an array that would look like this:
[
{ id: 1, name: "Bob" },
{ id: 2, name: "Daryl" }
]
I don't care which objects are left, as long as each ID is unique. Anything in Underscore, maybe, that would do this?
Edit: This is not the same as the duplicate listed below; I'm not trying to filter duplicate OBJECTS, but objects that contain identical IDs. I've done this using Underscore - I'll post the answer shortly.

You can use reduce and some to good effect here:
var out = arr.reduce(function (p, c) {
// if the next object's id is not found in the output array
// push the object into the output array
if (!p.some(function (el) { return el.id === c.id; })) p.push(c);
return p;
}, []);
DEMO

the es6 way
function removeDuplicates(myArr, prop) {
return myArr.filter((obj, pos, arr) => {
return arr.map(mapObj => mapObj[prop]).indexOf(obj[prop]) === pos
})
}
Test it
let a =[
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
console.log( removeDuplicates( a, 'id' ) )
//output [
{ id: 1, name: "Bob" },
{ id: 2, name: "Daryl" }
]

If you use underscore, you can use the _uniq method
var data = [
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
_.uniq(data, function(d){ return d.ID });
Produces a duplicate-free version of the array, using === to test object equality. In particular only the first occurence of each value is kept. If you know in advance that the array is sorted, passing true for isSorted will run a much faster algorithm. If you want to compute unique items based on a transformation, pass an iteratee function.
Source: http://underscorejs.org/#uniq

Can use es6 Map collection mix with reduce
const items = [
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
]
const uniqItems = [...items.reduce((itemsMap, item) =>
itemsMap.has(item.id) ? itemsMap : itemsMap.set(item.id, item)
, new Map()).values()]
console.log(uniqItems);

Using findIndex should be the simplest solution.
array.filter((elem, index, arr) => arr.findIndex(e => e.id === elem.id) === index)

You can simply filter the array, but you'll need an index of existing IDs that you've already used...
var ids = [];
var ar = [
{ id: 1, name: "Bob" },
{ id: 1, name: "Donald" },
{ id: 2, name: "Daryl" }
];
ar = ar.filter(function(o) {
if (ids.indexOf(o.id) !== -1) return false;
ids.push(o.id);
return true;
});
console.log(ar);
Here's some documentation on filter()...
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

Related

i need a help how can i treat maximum call stack?

Thanks i fixed some sentence by advice. my code is like that,
i wanna find object with id. but if not, I want to return 'null'
function ha7(arr, id) { // i wanna find object with id
let result = [];
for(let i = 0 ; i < arr.length ; i++) {
if(arr[i].id === id) {
return arr[i] // found id, then return included object
}
else if(Array.isArray(arr[i].children)){ // but , its array
// let ar = ha7(arr[i].children, id)
result.push(...arr[i].children) // i put 'arr[i].children' to variables
}
}
if (result.id === id) {
return result // find object with id in inner
} else {
return ha7(result, id) // cant find. then go ahead!
}
return null // all of none exist id is return null
}
it is testing array.
let input = [
{
id: 1,
name: 'johnny',
},
{
id: 2,
name: 'ingi',
children: [
{
id: 3,
name: 'johnson',
},
{
id: 5,
name: 'steve',
children: [
{
id: 6,
name: 'lisa',
},
],
},
{
id: 11,
},
],
},
{
id: '13',
},
];
output = ha7(input, 5);
console.log(output); // --> { id: 5, name: 'steve', children: [{ id: 6, name: 'lisa' }] }
output = ha7(input, 99);
console.log(output); // --> null
I tried a lot of trial, like that. i wanna know.
how can i treat maximum call stack ?
and i wanna return 'null' value.
function ha7(arr, id) { // i wanna find object with id
let result = [];
for(let i = 0 ; i < arr.length ; i++) {
if(arr[i].id === id) {
return arr[i] // found id, then return included object
}
else if(Array.isArray(arr[i].children)){ // but , its array
// let ar = ha7(arr[i].children, id)
result.push(...arr[i].children) // i put 'arr[i].children' to variables
}
}
if (result.id === id) {
return result // find object with id in inner
} else {
return ha7(result, id) // cant find. then go ahead!
}
return null // all of none exist id is return null
}
let input = [
{
id: 1,
name: 'johnny',
},
{
id: 2,
name: 'ingi',
children: [
{
id: 3,
name: 'johnson',
},
{
id: 5,
name: 'steve',
children: [
{
id: 6,
name: 'lisa',
},
],
},
{
id: 11,
},
],
},
{
id: '13',
},
];
output = ha7(input, 5);
console.log(output); // --> { id: 5, name: 'steve', children: [{ id: 6, name: 'lisa' }] }
output = ha7(input, 99);
console.log(output); // --> null
This code is the problem:
if (result.id === id) {
return result // find object with id in inner
} else {
return ha7(result, id) // cant find. then go ahead!
}
Two lines above this you initialize result as an array. Then in this conditional test you treat the array result as if it were an object. So, since result.id does not equal id, the else condition recurses for ever and ever.
I've taken a different, more functional approach to the task.
filter the array on the id
If there is a length then at least one was found
Return the first one
Next filter out all the objects with children
Then create an array (with .map() that only includes the children
This will create an array of arrays, so must flatten it
If there are no children, then id was not found
Return null
Recurse the children
let input=[{id:1,name:"johnny"},{id:2,name:"ingi",children:[{id:3,name:"johnson"},{id:5,name:"steve",children:[{id:6,name:"lisa"}]},{id:11}]},{id:"13"}];
function ha7(arr, id) {
let found = arr.filter(o => o.id === id);
if (found.length) return found[0]; // return first match
let children = arr.filter(o=>!!o.children).map(c=>c.children).flat();
if(!children.length) return null;
return ha7(children, id);
}
output = ha7(input, 5);
console.log(output); // --> { id: 5, name: 'steve', children: [{ id: 6, name: 'lisa' }] }
output = ha7(input, 99);
console.log(output); // --> null

Delete multiple objects in an array by id

I have a main array of objects with each object having some key/values as well as a "id" key with 1,2,3,4,5, etc
Now I have another array representing just id's (like [2,3])
I want to use this array to delete objects from the main array...so in this case, objects from the main array having id's 2 & 3 should be deleted
While I am aware of findBy(id), I am not sure if that can be used to delete multiple objects at once.
You can use filter. In the filter callback function check if the id is also there in id array by using includes
let idArr = [1, 2]
let obj = [{
id: 1,
name: 'abc'
},
{
id: 2,
name: 'abc'
},
{
id: 3,
name: 'abc'
},
{
id: 4,
name: 'abc'
}
];
let data = obj.filter(item => !idArr.includes(item.id));
console.log(data);
console.log(obj)
using filter might work well here. you could write something like:
var newArray = oldArray.filter(object => !ids.includes(object.id))
You can do it, like this:
[2,3].forEach(key => {
delete object[key];
})
You can use filter method for this.
Ex:
let id = 2;
let list = [{
Id: 1,
Name: 'a'
}, {
Id: 2,
Name: 'b'
}, {
Id: 3,
Name: 'c'
}];
let lists = list.filter(x => {
return x.Id != id;
})
console.log(lists);
Assuming you want to delete items from the original array by entirely removing the element from the array (and you don't want to get a new array), you can take advantage of
Array.splice
let idArr = [1, 2];
let obj = [{
id: 1
},
{
id: 2
},
{
id: 3
},
{
id: 4
}
];
for (let id of idArr) {
// look for the element by its id.
const objIdRef = obj.find(i => i.id === id);
// if it actually exists, splice it.
objIdRef && obj.splice(obj.indexOf(objIdRef), 1);
}
console.log(obj);
If the obj array is big, you might want to make a map from it before processing the id array, so that the complexing is reduced to O(1) when the delete process begins.
Perhaps This is what you want:
var arr= [{id:1, name: "foo"}, {id:2, name: "bar"}, {id:3, name:"not to be deleted"}];
var idsToDelete = [1, 2];
var res = arr.map((i, idx)=>{
return arr[idx] = idsToDelete.includes(i.id)? undefined : arr[idx]
}).filter(i=>i)
console.log(res)
You can try Lodash.js functions _.forEach() and _.remove()
let valuesArr = [
{id: 1, name: "dog"},
{id: 2, name: "cat"},
{id: 3, name: "rat"},
{id: 4, name: "bat"},
{id: 5, name: "pig"},
];
let removeValFromIndex = [
{id: 2, name: "cat"},
{id: 5, name: "pig"},
];
_.forEach(removeValFromIndex, (indi) => {
_.remove(valuesArr, (item) => {
return item.id === indi.id;
});
})
console.log(valuesArr)
/*[
{id: 1, name: "dog"},
{id: 3, name: "rat"},
{id: 4, name: "bat"},
]; */
Don't forget to clone (_.clone(valuesArr) or [...valuesArr]) before mutate your array

Find Duplicate Object in List and Add Parameters

I am trying to find duplicate objects in a list of objects and add new parameters to the duplicate one.
Below snipped code is what I implemented so far. The problem is that it adds desired parameters to every object in the list.
const list = [{
id: 1,
name: 'test1'
},
{
id: 2,
name: 'test2'
},
{
id: 3,
name: 'test3'
},
{
id: 2,
name: 'test2'
}
];
const newList = list.reduce(
(unique, item) => (unique.includes(item) ? unique : [...unique, {
...item,
duplicated: true,
name: `${item.name}_${item.id}`
}]), []
);
console.log(newList);
Since there are two duplicate objects by id, the duplicated one should have duplicated and new name parameters. What part is wrong in my implementation?
By using findIndex method:
const list = [{
id: 1,
name: 'test1'
},
{
id: 2,
name: 'test2'
},
{
id: 3,
name: 'test3'
},
{
id: 2,
name: 'test2'
}
];
const newList = list.reduce(
(unique, item) => (unique.findIndex(x => x.id === item.id) > -1 ? [...unique, {
...item,
duplicated: true,
name: `${item.name}_${item.id}`
}] : [...unique, item]), []);
console.log(newList);
It can be written simply:
const
list = [
{ id: 1, name: 'test1' },
{ id: 2, name: 'test2' },
{ id: 3, name: 'test3' },
{ id: 2, name: 'test2' }
],
uniqueList = list.reduce((arr, { id, name }) =>
arr.concat({
id,
name,
...arr.some(item => id === item.id) && { duplicate: true, name: `${name}_${id}` }
}), []);
console.log(uniqueList);
The problem was that when you called includes you were actually looking for an object whose pointer exists in the array.
In order to find an object which has property are the same as a requested property, you have no choice but to use functions such as some or every that is different from includes - you can send them a callback and not just an object.

Flatten nested array with key value pairs in Javascript

Given an array like this:
[
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com" },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"]
]
How could I use Javascript to reduce this to an array like this:
[
{ id: 1, emailAddress: "bill#test.com" },
{ id: 1, emailAddress: "bob#test.com" },
{ id: 2, emailAddress: "sarah#test.com" },
{ id: 3, emailAddress: "jane#test.com" },
{ id: 3, emailAddress: "laura#test.com" },
{ id: 3, emailAddress: "paul#test.com" }
]
I've read about the functions reduce, flat, map and so on and read lots of the questions on SO about using them but I can't find anything that's asking quite the same as this and I can't get my head around using those functions to do it.
You could use flatMap
const input = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"] }
]
const output = input.flatMap(o =>
o.emailAddresses.map(e => ({ id: o.id, emailAddress: e }) )
)
console.log(output)
If flatMap is not supported, you could use a nested for...of loop:
const input = [{id:1,emailAddresses:["bill#test.com","bob#test.com"]},{id:2,emailAddresses:["sarah#test.com"]},{id:3,emailAddresses:["jane#test.com","laura#test.com","paul#test.com"]}];
const output = []
for (const { id, emailAddresses } of input)
for (const emailAddress of emailAddresses)
output.push({ id, emailAddress })
console.log(output)
You can map over your data and then use reduce to flatten the resulting array:
const result = data
.map(datum => {
return datum.emailAddresses.map(emailAddress => {
return { id: datum.id, emailAddress };
});
})
.reduce((result, current) => {
return [...result, ...current];
}, []);
We can use Array.prototype.reduce to go over each object in the array and take into consideration the multiple values in the emailAddress property array and create separate object for each one and finally accumulate the new objects in the new array (r):
const data = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"]}
]
const flat = data.reduce((r, e) => {
e.emailAddresses.forEach((obj) => r.push({id: e.id, emailAddresses : obj }));
return r;
}, []);
console.log(flat);
You can use reduce and map
const data = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"]}
]
const flat = (toFlatten) =>
toFlatten.reduce((r,c)=> {
r.push(...c.emailAddresses.map(email=>({id: c.id, emailAddress: email})))
return r
}, [])
console.log(flat(data))
Here is a solution that doesn't use any array prototype but does, instead, take advantage of function generators.
The script below iterates the array, acquire all keys of the element except emailAddresses, which is handled separately, and for each email address it yields an object filled with the single email address and the rest of the data.
This solution iterate the original array only once.
Because it uses function generators, this solution is widely supported, it just won't work on IE due it's lack of support for function generators, despite babel or TSC can easily add compatibility to that.
const input = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"] }
];
function* flattenEmailAddresses(arr) {
for (var {emailAddresses, ...keys} of arr) {
for (var emailAddress of emailAddresses) yield {...keys, emailAddress};
}
}
console.log([...flattenEmailAddresses(input)]);

add an object to an array if it doesn't have the same key with one of the objects in the array

I'm using Lodash. I have the array below:
const array = [{id:1,name:a},{id:2,name:b},{id:3,name:c},{id:4,name:d},{id:5,name:e}];
and I'm about to add another object to this array but before that, I need to check if the new object's name is already in the array or not and if there is one with the name I won't add the new object anymore.
I know some ways to do it, for instance, a loop with _.map, but want to make sure if there is an easier way.
You could use Lodash's some which if provided with an appropriate predicate e.g. (item => item.name === newName) will return a boolean indicating whether or not the item already exists (in this case, true would mean the name already exists). The benefit of using this over other iterating methods is that it will stop as soon as it finds one that returns true resulting in better performance.
With native javascript , you can use findIndex, this will return the index of the object where the name matches. If it returns -1 then there is no such object with same name. In that case update the array.
const array = [{
id: 1,
name: 'a'
}, {
id: 2,
name: 'b'
}, {
id: 3,
name: 'c'
}, {
id: 4,
name: 'd'
}, {
id: 5,
name: 'e'
}];
let newObjToAdd = {
id: 1,
name: 'z'
};
let newObjNotToAdd = {
id: 1,
name: 'a'
}
function updateArray(obj) {
let k = array.findIndex((item) => {
return item.name === obj.name;
})
if (k === -1) {
array.push(obj)
} else {
console.log('Array contains object with this name')
}
}
updateArray(newObjToAdd);
console.log(array)
updateArray(newObjNotToAdd);
You don't need lodash for some. You get that with native JS too (ES6):
const array = [{id:1,name:'a'},{id:2,name:'b'},{id:3,name:'c'},{id:4,name:'d'},{id:5,name:'e'}];
console.log(array.some(e => e.name === 'a'));
if (!array.some(e => e.name === 'z')) {
array.push({id: 5, name: 'z'});
}
console.log(array);
Doing this with lodash is few chars shorter but here is how you could do it with ES6 and Array.some:
const array = [{ id: 1, name: "A" }, { id: 2, name: "B" }, { id: 3, name: "C" }, { id: 4, name: "D" }, { id: 5, name: "C" }];
const maybeUpdate = (arr, obj) => {
if(!array.some(x => x.id == obj.id))
array.push(obj)
}
maybeUpdate(array, {id: 2, name: "F"}) // id exists wont insert
maybeUpdate(array, {id: 12, name: "F"}) // will insert
console.log(array)
Same idea with lodash and _.some would be:
const array = [{ id: 1, name: "A" }, { id: 2, name: "B" }, { id: 3, name: "C" }, { id: 4, name: "D" }, { id: 5, name: "C" }];
const maybeUpdate = (arr, obj) => {
if(!_.some(array, {id: obj.id}))
array.push(obj)
}
maybeUpdate(array, {id: 2, name: "F"}) // id exists wont insert
maybeUpdate(array, {id: 12, name: "F"}) // will insert
console.log(array)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Note that you could also use various other ways to get the same result. Array.find or _.find would work as well since all you have to do is to check if there was a hit:
const maybeUpdate = (arr, obj) => {
if(!_.find(array, {id: obj.id})) // or if(!array.find(x => x.id == obj.id))
array.push(obj)
}

Categories

Resources