How to merge two arrays in JavaScript and remove the duplicate items? - javascript

I have two arrays
var array1 = [{firstName:'John', age:32, email:'xyz1#email.com'}, {firstName:'Jack', age:40, email:'xyz2#email.com'}];
var array2 = [{firstName:'Jen', age:29, email:'abc1#email.com'}, {firstName:'Jack', age:40, email:'xyz2#email.com'}];
I would like to merge the two arrays and get the below output
var array3 = [{firstName:'John', age:32, email:'xyz1#email.com'}, {firstName:'Jack', age:40, email:'xyz2#email.com'}, {firstName: 'Jen', age: 29, email: 'abc1#email.com'}];
Would like to know best way to merge two arrays in JavaScript so that I get only the unique items from each array?

Answer
You can do this using Rest Operators, the Set Object and JSON methods:
[ ...new Set([...a1, ...a2].map(JSON.stringify)) ].map(JSON.parse);
Example
var array1 = [{firstName:'John', age:32, email:'xyz1#email.com'}, {firstName:'Jack', age:40, email:'xyz2#email.com'}];
var array2 = [{firstName:'Jen', age:29, email:'abc1#email.com'}, {firstName:'Jack', age:40, email:'xyz2#email.com'}];
function joinArrays(a1, a2) {
return [ ...new Set([...a1, ...a2].map(JSON.stringify)) ].map(JSON.parse);
}
console.log(joinArrays(array1, array2));
Explanation:
You combine Arrays by using Rest Operators:
[...a1, ...a2]
You turn the Objects into Strings with JSON.stringify:
[...a1, ...a2].map(JSON.stringify)
You place these Strings into a Set. Sets do not allow duplicate Primitive Values( strings, numbers, etc )
new Set([...a1, ...a2].map(JSON.stringify))
You use the Rest Operator to place the Set into an Array
[...new Set([...a1, ...a2].map(JSON.stringify))]
You map the Array using JSON.parse to turn the strings back into Objects, minus duplicates.
[ ...new Set([...a1, ...a2].map(JSON.stringify)) ].map(JSON.parse);

You can use the reduce method to merge without duplicates and ensure the mapping is efficient in-case the arrays are long
First you create a map of the elements in the first array with the key firstName. This will save on performance for large arrays/lists
const array1Map = array1.reduce(
(state, payload) => ({...state, [payload.firstName]: payload})
)
Then you create a new array by merging the two arrays but check against array1Map for duplicates using the reduce function again
const mergedArray = array2.reduce((state, payload) => {
if (!array1Map[payload.firstName]) {
return [...state, payload]
}
return state
}, array1)
The new array is a combination of the 2 arrays without duplicates
var array1 = [{
firstName: 'John',
age: 32,
email: 'xyz1#email.com',
},
{
firstName: 'Jack',
age: 40,
email: 'xyz2#email.com',
}
]
var array2 = [{
firstName: 'Jen',
age: 29,
email: 'abc1#email.com',
},
{
firstName: 'Jack',
age: 40,
email: 'xyz2#email.com',
}
]
const array1Map = array1.reduce(
(state, payload) => ({ ...state,
[payload.firstName]: payload
})
)
const mergedArray = array2.reduce((state, payload) => {
if (!array1Map[payload.firstName]) {
return [...state, payload]
}
return state
}, array1)
console.log(mergedArray)

Related

How to filter few properties of an object which is in Array of objects if one property equals property from another object

I have a object which has some properties for one user, and I have array of objects which is returned from API.
My goal is to check which object of Array of objects has the same property as the one single initial object, and then it should return only part of it's properities.
I have tried to use .map on Array of objects but it seems not workig.
Below is the code example. I have also prepared codesandbox if You wish.
const user =
{
name: "jan",
lastName: "kowalski",
fullName: "jan kowalski",
car: "audi"
}
;
const usersAnimal = [
{
name: "jan",
lastName: "kowalski",
fullName: "jan kowalski",
animal: "cat",
animalSize: "small",
animalName: "Bat"
},
{
name: "john",
lastName: "smith",
fullName: "john smith",
animal: "dog",
animalSize: "middle",
animalName: "Jerry"
},
{
name: "Anna",
lastName: "Nilsson",
fullName: "Anna Nilsson",
animal: "cow",
animalSize: "big",
animalName: "Dorrie"
}
];
const filtered = usersAnimal.map((userAnimal)=>userAnimal.fullName === user.fullName && return userAnimal.animalName & userAnimal.animalSize & userAnimal.animal);
thanks
https://codesandbox.io/s/admiring-edison-qxff42?file=/src/App.js
For case like this, it would be far easier if you filter it out first then proceed using map:
const filtered = usersAnimal
.filter((animal) => animal.fullName === user.fullName)
.map(({ animalName, animalSize, animal }) => {
return {
animalName,
animalSize,
animal
};
});
I am providing a for loop solution as I haven't learnt many array methods in javascript.
For me the simplest option is to use a for loop and an if check to loop through the arrays values to check for included values.
for (let v in usersAnimal) {
if (usersAnimal[v].fullName === user.fullName) {
console.log(usersAnimal[v])
}
}
The code above will log the entire usersAnimal object containing the fullname we are looking for.
{
name: 'jan',
lastName: 'kowalski',
fullName: 'jan kowalski',
animal: 'cat',
animalSize: 'small',
animalName: 'Bat'
}
commented for further understanding
for (let v in usersAnimal) {
//loops though the array
if (usersAnimal[v].fullName === user.fullName) {
//when the index value 'v' has a fullname that matches the user fullname value
// it passes the if check and logs that object value
return console.log(usersAnimal[v])
//return true...
}
//return null
}
//etc
If you want to filter, I recommend you to use filter.
The map method will create a new array, the content of which is the set of results returned by each element of the original array after the callback function is operated
const user = {name:"jan",lastName:"kowalski",fullName:"jan kowalski",car:"audi"};
const usersAnimal = [{name:"jan",lastName:"kowalski",fullName:"jan kowalski",animal:"cat",animalSize:"small",animalName:"Bat"},{name:"john",lastName:"smith",fullName:"john smith",animal:"dog",animalSize:"middle",animalName:"Jerry"}];
// Get an array of matching objects
let filtered =
usersAnimal.filter(o => o.fullName === user.fullName);
// You get the filtered array, then you can get the required properties
filtered.forEach(o => {
console.log(
'animal:%s, animalSize:%s, animalName:%s',
o?.animal, o?.animalSize, o?.animalName
);
});
// Then use map to process each element
filtered = filtered.map(o => {
const {animal, animalSize, animalName} = o;
return {animal, animalSize, animalName};
});
console.log('filtered', filtered);

Javascript, filtering array of objects and return all the unique objects

I have an array of objects, that contains properties that are objects:
let allPersons = [
{ id: "abcdefg",
name: "tom",
...
phone: {
brand: "blah"
id: "hijklm"
...
}
},
{ id: ....}, {...}, {...}
];
What I need to do is filter those objects and returning all the phones, filtering them by id so all phones returned are unique.
I tried to retrieve first all the phones:
// allPersons is the full array mentioned above
let phones = [...new Set(allPersons.map(person => person.phone))];
then I tried to return all the unique phones, but unsuccessfully:
let result = phones.map(phone => phone.id).filter((value, index, self) => self.indexOf(value) === index)
This returns only the unique ids of the phones, but I want the entire object. What can I do?
UPDATE:
phone Ids are NOT unique, e.g. nokia3310 has id 1, nokia3330 has id 2, etc: so tom and john can have the same phone and phone ids could be duplicated!
Make an object indexed by IDs instead, then take the object's values:
const phonesById = Object.fromEntries(
allPersons.map(
({ phone }) => [phone.id, phone]
)
);
const uniquePhones = Object.values(phonesById);
If you're trying to get the phone object in each object of the array, then the code below will do that for you.
It gets the phone object and stores it in a
var objArr = [
{id: "abcdefg", name: "tom", phone: {brand: "blah", id: "hijklm"}},
{id: "guidiuqwbd", name: "john", phone: {brand: "hihihih", id: "ayfva"}},
{id: "yuygeve", name: "doe", phone: {brand: "hahahah", id: "cqcqw"}}
]
var allPhoneObjects = [];
objArr.forEach(function(currObj){
var phoneObj = currObj.phone;
allPhoneObjects.push(phoneObj);
});
console.log(allPhoneObjects);
I propose you the following solution
let uniques = new Set();
const phones = allPersons.filter(({phone}) => (
uniques.has(phone.id) ? false : !!uniques.add(phone.id)
)).map(p => p.phone)
Basically, we define a Set to record ids of the phones already processed, and a filter function on the allPersons array, that returns only the phones not already in the Set. We complete with the map to extract only the portion of JSON needed
EDIT
You can use just one function on the allPersons array using the reduce function
let uniques = new Set();
const phones = allPersons.reduce( (filtered, {phone}) => {
if (!uniques.has(phone.id)) {
filtered.push(phone);
uniques.add(phone.id);
}
return filtered
}, [])

Populating a New Array from Corresponding Index of Elements from 3 Arrays

I have been trying to resolve an issue where my conditional logic doesn't work when I have the same string value in two elements of an array. I have been trying it with for-loops, but without success.
What occurs to me after thinking about it is that the best way to handle this is to take my three arrays - of which, in my use case, there will ALWAYS be an equal number of elements, and mash them together into a new array of objects -- taking the corresponding value from each array element to popular the new array of objects.
Imagine data like this:
const goalScorers = ['John Smith', 'Dave Jones', 'Rob Porter'];
const goalTimes = [4, 23, 56];
const goalTypes = ['penalty', 'breakaway', 'header'];
How best should I handle this to end up with array like this:
const combinedArr = [
{ scorer: 'John Smith', time: 4, type: 'penalty' },
{ scorer: 'Dave Jones', time: 23, type: 'breakaway' },
{ scorer: 'Rob Porter', time: 56, type: 'header' }
]
Map over one of the arrays. The callback function receives the array index, it can use that to access the corresponding elements of the other arrays.
const combinedArr = goalScorers.map((scorer, i) => ({
scorer: scorer, time: goalTimes[i], type: goalTypes[i]
}));
If all the arrays are sorted correctly, so each index is the same instance in every array than you can parse one with map and use index to populate other fields
const goalScorers = ['John Smith', 'Dave Jones', 'Rob Porter'];
const goalTimes = [4, 23, 56];
const goalTypes = ['penalty', 'breakaway', 'header'];
const newArray = goalScorers.map((element, index) => {
return {
scorer: element,
time: goalTimes[index],
type: goalTypes[index],
}
})
console.log(newArray)

Split each element in array into object after certain character

I'm new to node.js and javascript. I have the following array:
var oldarray = [
'name1\tstreet\tperson\tphone1\tphone2\nname2\street2\tperson1\tphone82\tphone3\n'
]
Note, this is a single element array. First, I require the array to contain a new element after each new line first, then, re-format like below:
let headers = {
name: "",
street: "",
person: "",
phone 1 "",
phone 2 ""
}
How can I parse through each element (after creating a new element after each +), and assign an object within an array after each instance of \
The desired output is this:
[{
name: 'name1',
street: 'street2',
person: 'person1',
phone1: 'phone82 ',
phone2: 'phone3'
},
{
name: 'name2',
street: 'street2',
person: 'person1',
phone1: 'phone1 ',
phone2: 'phone2'
}]
Any help is highly appreciated.
If you have the same structure for all items in OLD_ARRAY you can use map, filter and reduce in order to manipulate your input.
So what I did?
In case that you have multiple strings like the example input (more than 1 array item) I convert it to sub-arrays of each string by using map and split by \n, which is your string separator. Than I filtered it by strings that are not empty (becasue that you have a post-fix of \n as well).
From each sub-array I extracted all the contacts using extractContacts function - it splites the sub-array by your separaotr, \t, and map it according to your contacts temaplte.
Since it's a format of array of arrays, I used reduce to concat all the arrays together
const OLD_ARRAY = [
'name1\tstreet\tperson\tphone1\tphone2\n' +
'name2\tstreet2\tperson1\tphone82\tphone3\n'
];
function extractContacts(templates) {
return templates.map(t => t.split('\t'))
.map(details => ({
name: details[0],
street: details[1],
person: details[2],
phone1: details[3],
phone2: details[4]
}));
}
let contacts = OLD_ARRAY.map(str => str.split('\n').filter(str => str !== ''))
.map(template => extractContacts(template))
.reduce((a, acc) => acc.concat(a), []);
console.log(contacts)
You can split each oldarray value on \n and then \t into newarray, and then use Object.fromEntries to build an object from each newarray value, combining the split values with each key from headers:
var oldarray = [
'name1\tstreet\tperson\tphone1\tphone2\n' +
'name2\tstreet2\tperson1\tphone82\tphone3\n'
]
let newarray = [];
oldarray.forEach(s => s.trim().split('\n').map(v => newarray.push(v.split('\t'))));
let headers = {
'name': "",
'street': "",
'person': "",
'phone 1': "",
'phone 2': ""
}
let keys = Object.keys(headers);
out = newarray.map(s => Object.fromEntries(s.map((v, i) => [keys[i], v])));
console.log(out);
First split the array by \n to get individual paths and then split them by \t, and use reduce to create new header objects from each subarray
var oldarray = [
'name1\tstreet\tperson\tphone1\tphone2\n' +
'name2\tstreet2\tperson1\tphone82\tphone3\n' +
'name4\tstreet4\tperson4\tphone84\tphone4\n'
]
arr = oldarray.flatMap(o => o.split("\n"))
c = arr.map(o => o.split("\t"))
c.pop()
result = c.reduce((acc,[name, street, person, phone1, phone2],i) => {
acc = [...acc,{name:name,street:street,person:person,phone1:phone1,phone2:phone2}]
return acc
},[])
console.log(result)

Merge objects with corresponding key values from two different arrays of objects

I've got two arrays that have multiple objects
[
{
"name":"paul",
"employee_id":"8"
}
]
[
{
"years_at_school": 6,
"department":"Mathematics",
"e_id":"8"
}
]
How can I achieve the following with either ES6 or Lodash?
[
{
"name":"paul",
"employee_id":"8"
"data": {
"years_at_school": 6
"department":"Mathematics",
"e_id":"8"
}
}
]
I can merge but I'm not sure how to create a new child object and merge that in.
Code I've tried:
school_data = _.map(array1, function(obj) {
return _.merge(obj, _.find(array2, {employee_id: obj.e_id}))
})
This merges to a top level array like so (which is not what I want):
{
"name":"paul",
"employee_id":"8"
"years_at_school": 6
"department":"Mathematics",
"e_id":"8"
}
The connector between these two is "employee_id" and "e_id".
It's imperative that it's taken into account that they could be 1000 objects in each array, and that the only way to match these objects up is by "employee_id" and "e_id".
In order to match up employee_id and e_id you should iterate through the first array and create an object keyed to employee_id. Then you can iterate though the second array and add the data to the particular id in question. Here's an example with an extra item added to each array:
let arr1 = [
{
"name":"mark",
"employee_id":"6"
},
{
"name":"paul",
"employee_id":"8"
}
]
let arr2 = [
{
"years_at_school": 6,
"department":"Mathematics",
"e_id":"8"
},
{
"years_at_school": 12,
"department":"Arr",
"e_id":"6"
}
]
// empObj will be keyed to item.employee_id
let empObj = arr1.reduce((obj, item) => {
obj[item.employee_id] = item
return obj
}, {})
// now lookup up id and add data for each object in arr2
arr2.forEach(item=>
empObj[item.e_id].data = item
)
// The values of the object will be an array of your data
let merged = Object.values(empObj)
console.log(merged)
If you perform two nested O(n) loops (map+find), you'll end up with O(n^2) performance. A typical alternative is to create intermediate indexed structures so the whole thing is O(n). A functional approach with lodash:
const _ = require('lodash');
const dataByEmployeeId = _(array2).keyBy('e_id');
const result = array1.map(o => ({...o, data: dataByEmployeeId.get(o.employee_id)}));
Hope this help you:
var mainData = [{
name: "paul",
employee_id: "8"
}];
var secondaryData = [{
years_at_school: 6,
department: "Mathematics",
e_id: "8"
}];
var finalData = mainData.map(function(person, index) {
person.data = secondaryData[index];
return person;
});
Sorry, I've also fixed a missing coma in the second object and changed some other stuff.
With latest Ecmascript versions:
const mainData = [{
name: "paul",
employee_id: "8"
}];
const secondaryData = [{
years_at_school: 6,
department: "Mathematics",
e_id: "8"
}];
// Be careful with spread operator over objects.. it lacks of browser support yet! ..but works fine on latest Chrome version for example (69.0)
const finalData = mainData.map((person, index) => ({ ...person, data: secondaryData[index] }));
Your question suggests that both arrays will always have the same size. It also suggests that you want to put the contents of array2 within the field data of the elements with the same index in array1. If those assumptions are correct, then:
// Array that will receive the extra data
const teachers = [
{ name: "Paul", employee_id: 8 },
{ name: "Mariah", employee_id: 10 }
];
// Array with the additional data
const extraData = [
{ years_at_school: 6, department: "Mathematics", e_id: 8 },
{ years_at_school: 8, department: "Biology", e_id: 10 },
];
// Array.map will iterate through all indices, and gives both the
const merged = teachers.map((teacher, index) => Object.assign({ data: extraData[index] }, teacher));
However, if you want the data to be added to the employee with an "id" matching in both arrays, you need to do the following:
// Create a function to obtain the employee from an ID
const findEmployee = id => extraData.filter(entry => entry.e_id == id);
merged = teachers.map(teacher => {
const employeeData = findEmployee(teacher.employee_id);
if (employeeData.length === 0) {
// Employee not found
throw new Error("Data inconsistency");
}
if (employeeData.length > 1) {
// More than one employee found
throw new Error("Data inconsistency");
}
return Object.assign({ data: employeeData[0] }, teacher);
});
A slightly different approach just using vanilla js map with a loop to match the employee ids and add the data from the second array to the matching object from the first array. My guess is that the answer from #MarkMeyer is probably faster.
const arr1 = [{ "name": "paul", "employee_id": "8" }];
const arr2 = [{ "years_at_school": 6, "department": "Mathematics", "e_id": "8" }];
const results = arr1.map((obj1) => {
for (const obj2 of arr2) {
if (obj2.e_id === obj1.employee_id) {
obj1.data = obj2;
break;
}
}
return obj1;
});
console.log(results);

Categories