ES6 Array methods to remove / detect duplicate objects in an array - javascript

I am trying to add an object to an array if the array already does not have that object.
So I have an array as follows
[{id:1},{id:2},{id:3}]
I want to check if a id:1 exist or not if not then add if yes then show an error or log a message.
I am able to achieve this using a simple array as follows.
let result =[1,2,2,3,1,4,1,4,2,3].filter((el, i, a) => i === a.indexOf(el));
I cannot figure out how to achive the same with array of objects.
Thanks

You can use some to check for duplicates like:
// array with duplicate objects {id:1}
let arr = [{id:1},{id:1},{id:2}]
function duplicateFound(arr){
const ids = arr.map(x => x.id);
return ids.some((item, idx) => ids.indexOf(item) != idx);
}
console.log(duplicateFound(arr));
// array with not duplicates
arr = [{id:1},{id:2},{id:3}]
console.log(duplicateFound(arr));

You can use Array#filter, and check the length:
const arr = [{id:1},{id:2},{id:3}];
const el = { id: 1 };
const exists = arr.filter(({ id }) => id === el.id).length > 0;
console.log(exists);
Or you can use Array#find, which has a slight advantage over Array#filter, since it will stop as soon as an item was found.
const arr = [{id:1},{id:2},{id:3}];
const el = { id: 1 };
const exists = !!arr.find(({ id }) => id === el.id);
console.log(exists);
You can wrap your array with a proxy that has a set trap, to prevent the insertion of duplicates automatically:
const arr = [{id:1},{id:2},{id:3}];
const arrayChangeHandler = {
set: function(target, property, value, receiver) {
if(property === 'length') {
return true;
}
const exists = !!target.find(({ id }) => id === value.id);
if(exists) {
console.log(`Id: ${value.id} exists!`); // you can return false here, and it will throw an error
} else {
target.push(value);
}
return true;
}
};
const pArr = new Proxy(arr, arrayChangeHandler);
pArr.push({ id: 1 });
pArr.push({ id: 10 });
console.log(JSON.stringify(arr));

You could try inserting all values as keys to a new array then flip keys & vals
let arr = "abccba".split('');
let res = [];
arr.forEach((n) => {
res[n] = n;
});
console.log(Object.keys(res));
A concern might be that if your values are numbers then you might need to recast them eg.
res = res.map(n) => +n

Related

Javascript array filter with conditional output / apply

I'm wondering, if there is a way to filter an array or stream and apply a function A to all matches and a function B to all non-matches in JavaScript. Here is some example code that explains it a bit more:
// initial data
var names = ['Matthias', 'Maria', 'Bob', 'Anton'];
var namesWithM;
var namesWithoutM;
// gets only names starting with M, but not the others at the same time
namesWithM = names.filter(name => name.startsWith('M'))
// conditional lambda version
namesWithM = [];
namesWithoutM = [];
names.forEach(name => name.startsWith('M') ? namesWithM.push(name) : namesWithoutM.push(name));
// conditional classical version
namesWithM = [];
namesWithoutM = [];
names.forEach(function(name) {
if (name.startsWith('M'))
namesWithM.push(name)
else
namesWithoutM.push(name);
});
The very first version handles just the matches but uses filter and not forEach. Is there any way to use filter and apply a function for matches and non-matches at once? Something like this pseudo code:
names.filter(name.startsWith('M')).apply(namesWithM::push).or(namesWithoutM::push);
filter returns an array. So you can use this array to fill with name which either starts with M or not.
In the below example the filter is filling the array with name starts with M. In filter callback the name not starting with M are filled in another array
// initial data
var names = ['Matthias', 'Maria', 'Bob', 'Anton'];
var namesWithM;
var namesWithoutM = [];
namesWithM = names.filter((name) => {
if (!name.startsWith('M')) {
namesWithoutM.push(name)
}
return name.startsWith('M');
});
console.log(namesWithM, namesWithoutM);
I would use reduce to group data into 2 mentioned cases. I don't see any reason to use filter here
let names = ['Matthias', 'Maria', 'Bob', 'Anton'];
let [namesWithM, namesWithoutM] = names.reduce((acc, name) => {
if (name.startsWith('M')) {
acc[0] = [...(acc[0] || []), name]
return acc;
}
acc[1] = [...(acc[1] || []), name]
return acc;
}, [])
// simpler version
console.log(namesWithM, namesWithoutM);
let [namesWithM1, namesWithoutM1] = names.reduce((acc, name) => {
const index = Number(!name.startsWith('M'));
acc[index] = [...(acc[index] || []), name];
return acc;
}, [])
console.log(namesWithM1, namesWithoutM1);
const names = ['Matthias', 'Maria', 'Bob', 'Anton'];
function A(item){
console.log('filtered');
console.log(item);
}
function B(item){
console.log('not-ffiltered');
console.log(item);
}
const filteredNames = names.filter(name => {
const isValid = name.startsWith('M')
if(isValid)
A(name)
else
B(name)
return isValid;
})

How to check if object with no names exists in array?

I am building a javascript function where I want to add an object to an array IF it is not already present, if it is I just want to update value.
My objects look like this:
[{31652237148248: 12}, {4365124714824: 4}]
How can I check the Array if there is an object with the ID of 31652237148248?
I tried this but it did not work:
var index = cartItems.findIndex((obj => obj[id] == id));
Use the in operator to check if the id is a property of the object:
const cartItems = [{31652237148248: 12}, {4365124714824: 4}];
const id = '4365124714824';
const index = cartItems.findIndex((obj => id in obj));
console.log(index);
You are looking to see if the value of objects property is equal to the id. 31652237148248 is never going to equal 12
so you can just do a type of
var index = cartItems.findIndex(obj => typeof obj[id] !== undefined);
you can do a truthy check - will fail if it is a falsey value.
var index = cartItems.findIndex(obj => obj[id]);
you can use object keys and includes or if first is equal
var index = cartItems.findIndex(obj => Object.keys(obj).includes(id));
var index = cartItems.findIndex(obj => Object.keys(obj)[0] === id);
A bunch of ways to do it
Personally a better way is just to use an object and not an array for the data.
var items = {31652237148248: 12, 4365124714824: 4}
const addItem = (id, count) => {
cartItems[id] = (cartItems[id] || 0) + count
}
const getArray = () =>
Object.entries(items).map(([key, count]) => ({ [key]: count }))
Try
let add = (obj,arr,k=Object.keys(obj)[0]) =>
arr.some( o=> (k in o)&&(o[k]=obj[k]) ) || arr.push(obj)
let data = [{31652237148248: 12}, {4365124714824: 4}]
let add = (obj,arr,k=Object.keys(obj)[0]) =>
arr.some( o=> (k in o)&&(o[k]=obj[k]) ) || arr.push(obj)
// TEST
add({31652237148248: 15}, data);
console.log('update',data);
add({666: 15}, data);
console.log('add',data);

filtering 2 arrays for unique elements

I write a function that receives 2 arrays and returns an array that has elements that exist in both arrays.
For example, if I pass [6,7,8,9] and [1,8,2,6], it should return [6,8].
My aim is not to use loops here.
I use this code:
const uniqueElements= (arr1, arr2) => {
return arr1.filter(it1=> arr2.filter((it2) => it2===it1).length>0)
}
However, if there are duplicate elements in arrays (e.g. [6,7,8,9,6] and [1,8,2,6,6]), it returns [6, 8, 6].
How should I mend my code so that it would return only unique elements without duplicates? Is it possible without using loops?
If you just want to get unique value which appears on both of the array, you just first change both of the array's to Set, loop through one Set and check if it's present on other or not, if it present return true from filter else return false,
const uniqueElements= (arr1, arr2) => {
let set1 = new Set(arr1)
let set2 = new Set(arr2)
return [...set1].filter(it1=> set2.has(it1))
}
console.log(uniqueElements([6,7,8,9],[1,8,2,6]))
console.log(uniqueElements([6,7,8,9,6],[1,8,2,6,6]))
Ref to read about Set
Set MDN
For your scenario use this -
constant uniqueElements= (arr1, arr2) => {
return Array.from(new Set(arr1.filter(it1=> arr2.filter((it2) => it2===it1).length>0)))
}
Hope this helps
Solution using Set from https://2ality.com/2015/01/es6-set-operations.html:
const uniqueElements = (arr1, arr2) => {
const a = new Set(arr1);
const b = new Set(arr2);
const intersection = new Set(
[...a].filter(x => b.has(x)));
return Array.from(intersection);
}
Simply you can just use Array#some() method to write the condition inside your Array#filter() method's callback:
const uniqueElements = (arr1, arr2) => {
let viewed = [];
return arr1.filter(it1 => {
let found = arr2.some((it2) => it2 === it1) && viewed.indexOf(it1) == -1;
viewed.push(it1);
return found;
});
}
Note:
This doesn't take duplicates, with the use of viewed array and viewed.indexOf(it1) == -1 condition.
Demo:
const uniqueElements = (arr1, arr2) => {
let viewed = [];
return arr1.filter(it1 => {
let found = arr2.some((it2) => it2 === it1) && viewed.indexOf(it1) == -1;
viewed.push(it1);
return found;
});
}
let a1 = [6,7,8,9,6];
let a2 = [1,8,2,6,6];
console.log(uniqueElements(a1, a2));

iterate through nested array to match IDs

I have a set of data which is nested arrays, these arrays may be empty or they may infact contain an ID, if one of the arrays ID's matches the ID im comparing it with, I want to take all of the data inside that array which matched and assign it to a variable to be used...
example:
data = [[],[],[],[],[],[],[],[],[{"id":"123","name":"DARES HOUSE 2019","startDate":null,"endDate":null,"country":null,"city":null,"type":"Event","members":null}],[],[],[],[],[],[],[],[],[],[],[]]
id = 123
matchedArray =
for (var i = 0; i < potentialEvents.length; i++) {
for (var j = 0; j < potentialEvents[i].length; j++) {
if (id === potentialEvents[i].id) {
return;
}
}
}
console.log(matchedArray)
I'm trying to have it so matchedArray will be the array with thhe matched IDs!!
if you can help, thank you a lot!
You can do this with a combination of .map, .filter and .flat
var data = [[],[],[],[],[],[],[],[],[{"id":"123","name":"DARES HOUSE 2019","startDate":null,"endDate":null,"country":null,"city":null,"type":"Event","members":null}],[],[],[],[],[],[],[],[],[],[],[]]
var id = 123;
var matchedArray = data.map( arr => {
return arr.filter(x => x.id == id);
}).flat();
console.log(matchedArray);
You can use Array#filter method to filter the inner array and Array#flatMap method to concatenate filtered array into one.
let data = [[],[],[],[],[],[],[],[],[{"id":"123","name":"DARES HOUSE 2019","startDate":null,"endDate":null,"country":null,"city":null,"type":"Event","members":null}],[],[],[],[],[],[],[],[],[],[],[]];
let id = 123;
let matchedArray = data.flatMap(arr => arr.filter(obj => obj.id == id))
console.log(matchedArray)
I'd recommend to use .some rather then .filter/.map/.flatMap. The main benefit is that it allows to stop traversing array when element is found.
On big arrays with a lot of data it will be more efficient (≈50 times faster): jsperf test
const data = [[],[],[],[],[],[],[],[],[{"id":"123","name":"DARES HOUSE 2019","startDate":null,"endDate":null,"country":null,"city":null,"type":"Event","members":null}],[],[],[],[],[],[],[],[],[],[],[]]
const id = 123;
let matchedArray = null;
data.some((a) => {
return a.some((v) => {
if (v != null && v.id == id) {
matchedArray = a;
return true;
}
});
});
console.log(matchedArray);

Cleaning the json object by removing duplicates and null and merging them into a single record

Cleaning the JSON object by removing duplicates and null and merging them into a single record
The json array looks like this:
var result =
[
{"id":"10035","occupation":null,"state":"FL"},
{"id":"10035","occupation":"doctor","state":null},
{"id":"10035","occupation":null,"state":null},
]
I want to merge records into one neglecting all the null fields and make it as a single record.Below is my expected output:
[
{"id":"10035","occupation":"doctor","state":"FL"}
]
You could do it with this ES6 script:
let data = [
{"id":"10035","occupation":null,"state":"FL"},
{"id":"10035","occupation":"doctor","state":null},
{"id":"10035","occupation":null,"state":null},
];
let result = Object.values(data.reduce ( (acc, {id, occupation, state}) => {
acc[id] = Object.assign({ id }, acc[id],
occupation && { occupation },
state && { state });
return acc;
}, {}));
console.log(result);
It will still produce multiple records if you have different id values in your input. When there are more than one non-null values for the other properties, but for the same id, then only the last one will survive.
When you're without support for Object.values
Use this definition of it:
Object.values = Object.values || (o => Object.keys(o).map(k => o[k]));
var final = {};
for (var i in result) {
for (var k in result[i]) {
if (result[i][k] && final[k] !== result[i][k]) {
final[k] = result[i][k];
}
}
}
console.log(final); // outputs: {id: "10035", state: "FL", occupation: "doctor"}
Here's a simple to understand example, which works for objects with any number of properties.
let data = [
{"id":"10035","occupation":null,"state":"FL"},
{"id":"10035","occupation":"doctor","state":null},
{"id":"10035","occupation":null,"state":null},
];
let result = data[0];
data.forEach(obj=> { // iterate through all objects in array
for(key in obj) // iterate through all properties of objects
if(obj[key]) result[key] = obj[key]; // if not null, assign to final result
});
console.log(result);
Here is a way to do it in O(n) time:
const mergeObjects = (data) => {
const dataWithoutDuplicates = {};
// first pass will get rid of dupes
let user;
for(let i = 0; i < data.length; data++) {
user = data[i];
if(!dataWithoutDuplicates[user.id]) {
dataWithoutDuplicates[user.id] = user
} else {
Object.keys(dataWithoutDuplicates[user.id]).forEach(key => {
if(dataWithoutDuplicates[user.id][key] === null && user[key]) {
dataWithoutDuplicates[user.id][key] = user[key]
}
})
}
return Object.values(dataWithoutDuplicates)
}

Categories