topic: JavaScript by Milos Protic relates to: Web Development on May, 11 2019
Handling Array Duplicates Can Be Tricky
Let's begin by defining a simple array:
const cars = [
'Mazda',
'Ford',
'Renault',
'Opel',
'Mazda'
]
As you can see, the first and the last item are the same. Finding this duplicate is straightforward considering that we have an array of items that are a primitive type. To achieve this, we can simply use the method filter
along with the indexOf
method within the provided callback, or new ES6 features like the spread operator and a Set
, for example.
// The old way
const unique = cars.filter((car, idx) => cars.indexOf(car) === idx);
console.log(unique); // outputs ['Mazda', 'Ford', 'Renault', 'Opel']
// The new way
const uniqueWithArrayFrom = Array.from(new Set(cars));
console.log(uniqueWithArrayFrom); // outputs ["Mazda", "Ford", "Renault", "Opel"]
const uniqueWithSpreadOperator = [...new Set(cars)];
console.log(uniqueWithSpreadOperator);// outputs ["Mazda", "Ford", "Renault", "Opel"]
Note that the indexOf
method will return the first occurrence of an item within the array. This is why we can compare the index returned by the indexOf
method with the current index in each iteration to see if the current item is a duplicate.
Finding Object Duplicates
This is the tricky part. Objects are compared via reference rather than the value or structure. This means that if we compare two objects that are exactly the same they won't match. We cannot simply do something like obj1 === obj2
because of the way they are compared.
const obj1 = {
name: 'John',
surname: 'Doe'
}
const obj2 = {
name: 'John',
surname: 'Doe'
}
const match = obj1 === obj2;
console.log(match) // outputs false
Now, what if we have an array of object duplicates? How are we going to filter out those? Considering what we've just read, it's not possible to use something simple like indexOf
.
Example array:
const names = [{
name: 'John',
surname: 'Doe'
}, {
name: 'Muhamed',
surname: 'Ali'
}, {
name: 'Mike',
surname: 'Tyson'
}, {
name: 'John',
surname: 'Doe'
}, {
name: 'John',
surname: 'Doe'
}, {
name: 'Mike',
surname: 'Tyson'
}, {
name: 'Mike',
surname: 'Tyson'
}];
As you can see, we have a couple of duplicate items. Let's implement the function that will find the duplicates.
The Longer Version
In this approach, we will manually loop through the source array (forEach
method) and check if each item exists in the resulting array by using the find
method.
Considering that we have an array of objects, we must compare each property of the current object in order to make sure that the items are the same. Broken down into steps the process looks like this:
- Get the object properties
- Define the resulting arrays (
unique
andduplicates
) - Loop through the source array
- Try to locate the current item within the
unique
array - If the item is found, push it into the
duplicates
otherwise into theunique
array
const findDuplicates = (source) => {
const keys = Object.keys(source[0]);
let unique = [], duplicates = [];
source.forEach((item, idx) => {
if(idx === 0) {
unique.push(item);
return;
};
const resultItem = unique.find(uniqueItem => {
let notFound = true;
keys.forEach(key => {
notFound = notFound &&
item[key] !== uniqueItem[key];
});
return !notFound;
});
(!resultItem ? unique : duplicates).push(item);
});
return { unique: unique, duplicates: duplicates };
};
const result = findDuplicates(names);
console.log(result.unique, result.duplicates);
// expected output
// unique items
// 0: {name: "John", surname: "Doe"}
// 1: {name: "Muhamed", surname: "Ali"}
// 2: {name: "Mike", surname: "Tyson"}
// duplicate items
// 0: {name: "John", surname: "Doe"}
// 1: {name: "John", surname: "Doe"}
// 2: {name: "Mike", surname: "Tyson"}
// 3: {name: "Mike", surname: "Tyson"}
A Bit Shorter Version
We could use the reduce
method in order to achieve the same thing. This is a very powerful method and it can be used to transform the array into the desired result. It accepts a callback as a parameter which is executed for each item in the array. The return value of the callback is the given accumulator modified within each iteration. Considering that this is not an article about the reduce
method, check out the official MDN documentation
Ok, back to our code. The modified version of the findDuplicates
method looks like this:
const findDuplicates = (source) => {
const keys = Object.keys(source[0]);
return source.reduce((acc, item) => {
const resultItem = acc.unique.find(uniqueItem => {
let notFound = true;
keys.forEach(key => {
notFound = notFound &&
item[key] !== uniqueItem[key];
});
return !notFound;
});
(!resultItem ? acc.unique : acc.duplicates).push(item);
return acc;
}, {
unique: [],
duplicates: []
})
};
The modified version should return the same resulting arrays as it did before.
// unique items
// 0: {name: "John", surname: "Doe"}
// 1: {name: "Muhamed", surname: "Ali"}
// 2: {name: "Mike", surname: "Tyson"}
// duplicate items
// 0: {name: "John", surname: "Doe"}
// 1: {name: "John", surname: "Doe"}
// 2: {name: "Mike", surname: "Tyson"}
// 3: {name: "Mike", surname: "Tyson"}
That's it. Thank you for reading and see you in the next article.
Further Reading
See this cheat sheet that will guide you through the most common use cases when it comes to the array manipulation.
Subscribe to get the latest posts delivered right to your inbox