DeDuplicating array of Objects javascript - javascript

Working on a project that is taking in 5 similar SQL databases, and I need to detect and filter out duplicates. I think I'm on the right track, but I'm not quite there yet. I am attempting to follow these steps to accomplish this:
start a .forEach() for the main array passing in an item object.
create a filtered array via let filtered = Array.filter(x => x.id !== item.id); to keep from checking against itself.
start a .forEach() for the filtered array passing in comparison as the parameter.
initialize variables for similarity in Name, Phone, and Email fields.(i.e.nameSimilarity, phoneSimilarity, and emailSimilarity)
If item.email and comparison.email aren't empty, compare the strings and store the similarity percentage in emailSimilarity else emailSimilarity=0.
If item.phone and comparison.phone aren't empty, compare the strings and store the similarity percentage in phoneSimilarity else phoneSimilarity=0.
Combine item.firstName and item.lastName into an variable called itemFullName and combine comparison.firstName and comparison.lastName into a variable called comparisonFullName.
If itemFullName and comparisonFullName aren't empty, compare the strings and store the similarity percentage in nameSimilarity else nameSimilarity=0.
if any of the percentages in emailSimilarity, nameSimilarity, or phoneSimilarity, add item plus the similarity variables and comparison.id to the duplicates array, and splice it out of the original array.
This is the code that I've written to follow these steps, but it appears that I'm getting duplicate entries in the duplicates array. I'm not sure why it's not working as expected, but I have a hunch that I can't really expect the original array to mutate inside the forEach() operation.
fullArray.forEach(item => {
let filtered = fullArray.filter(x => x.externalId !== item.externalId);
filtered.forEach(comparison => {
let emailSimilarity, phoneSimilarity, nameSimilarity;
if ((item.email !== '') && (comparison.email !== '')) {
emailSimilarity = strcmp.jaro(item.email, comparison.email);
} else {
emailSimilarity = 0;
}
if ((item.phone !== '') && (comparison.phone !== '')) {
phoneSimilarity = strcmp.jaro(item.phone, comparison.phone);
} else {
phoneSimilarity = 0;
}
let itemFullName = `${item.firstName} ${item.LastName}`.trim() || '';
let comparisonFullName = `${comparison.firstName} ${comparison.LastName}`.trim();
if (((itemFullName !== '') && (comparisonFullName !== '')) || ((itemFullName.indexOf('Group')! > 0) && (comparisonFullName.indexOf('Group') !>0))) {
nameSimilarity = strcmp.jaro(itemFullName, comparisonFullName);
} else {
nameSimilarity = 0;
}
if ((emailSimilarity || phoneSimilarity || nameSimilarity) > 0.89) {
let dupesOutput = Object.assign({}, item, { similarName: nameSimilarity, similarEmail: emailSimilarity, similarPhone: phoneSimilarity, similarTo: comparison.externalId });
dupes.push(dupesOutput);
fullArray = fullArray.filter(x => x.externalId !== item.externalId);
}
});
});
Where's the issue?

Assuming the similarity check is working, the problem is that you're reassigning a new array to fullArray while still being in the forEach loop of the old one.
I'd suggest you use Array.filter:
var filteredArray = fullArray.filter(item => {
return !fullArray.some(comparison => {
if(comparison.externalId==item.externalId)
return false;
let emailSimilarity, phoneSimilarity, nameSimilarity;
if ((item.email !== '') && (comparison.email !== '')) {
emailSimilarity = strcmp.jaro(item.email, comparison.email);
} else {
emailSimilarity = 0;
}
if ((item.phone !== '') && (comparison.phone !== '')) {
phoneSimilarity = strcmp.jaro(item.phone, comparison.phone);
} else {
phoneSimilarity = 0;
}
let itemFullName = `${item.firstName} ${item.LastName}`.trim() || '';
let comparisonFullName = `${comparison.firstName} ${comparison.LastName}`.trim();
if (((itemFullName !== '') && (comparisonFullName !== '')) || ((itemFullName.indexOf('Group')! > 0) && (comparisonFullName.indexOf('Group') !>0))) {
nameSimilarity = strcmp.jaro(itemFullName, comparisonFullName);
} else {
nameSimilarity = 0;
}
if ((emailSimilarity || phoneSimilarity || nameSimilarity) > 0.89) {
let dupesOutput = Object.assign({}, item, { similarName: nameSimilarity, similarEmail: emailSimilarity, similarPhone: phoneSimilarity, similarTo: comparison.externalId });
dupes.push(dupesOutput);
return true;
}else
return false;
});
});

Related

Replacing conditional chaining with an older method

I have some code that is using conditional chaining such as
productQuantity = purchases.filter((pObj) => pObj.elements[0]?.prodType?.id === purchase.elements[0]?.prodType?.id && pObj.sold === purchase.sold).length
It works fine but I need to convert the chained conditionals to an older method and I'm not sure how. Can anyone advise ?
Reason being that PM2 does not support chaining conditional operators.
Let's try this. Replacing all key?.key2 with key && key.key2
productQuantity = purchases.filter((pObj) => {
return pObj.elements[0] && pObj.elements[0].prodType && purchase.elements[0] && purchase.elements[0].prodType && pObj.elements[0].prodType.id === purchase.elements[0].prodType.id && pObj.sold === purchase.sold
}).length
I would probably do something like this.
First, we make the observation that the we are selecting an id from two similar objects: we can therefore refactor the logic required to select the id into a common function:
function selectId(item) {
if (item) {
const elements = item.elements;
if (elements) {
const element = elements[0];
if (element) {
const prodType = element.prodType;
if (prodType) {
const id = prodType.id;
return id;
}
}
}
}
}
You could also flatten the selection (which may or may not be more readable/maintainable):
function selectId(item) {
if (!item) return undefined;
const elements = item.elements;
if (!elements) return undefined;
const element = elements[0];
if (!element) return undefined;
const prodType = element.prodType;
if (!element) return undefined;
const id = prodType.id;
return id;
}
Once you have that, then your filter comes down to this:
productQuantity = purchases.filter( isMatch ).length;
function isMatch(obj) {
let itemId = selectId(obj);
let purchaseId = selectId(purchase);
const shouldKeep = itemId == purchaseId
&& obj.sold === purchase.sold;
return shouldKeep
}

Why does this my recursive function work after dividing a tag.class selector without dividing the matching tag.class new one?

I've been trying to do some html and css but I'm really bad at this so far. I was using a function that would check if two selectors match. A friend of mine came up with this code but neither of us fully understands how the return of the "tag.class" case works. My question is, if it doesn't divide the newSelector, how can it succesfully check the tag and class?
var matchFunctionMaker = function(selector) {
var selectorType = selectorTypeMatcher(selector);
var matchFunction;
if (selectorType === "id") {
matchFunction = nuevoSelector => '#' + nuevoSelector.id === selector;
} else if (selectorType === "class") {
matchFunction = nuevoSelector => {
var lista = nuevoSelector.classList;
for (let x of lista) {
if (selector === '.' + x) return true;
}
return false;
}
} else if (selectorType === "tag.class") {
matchFunction = nuevoSelector => {
var [tag, clase] = selector.split('.');
return matchFunctionMaker(tag) (nuevoSelector) && matchFunctionMaker(`.${clase}`) (nuevoSelector);
};
} else if (selectorType === "tag") {
matchFunction = nuevoSelector => nuevoSelector.tagName.toLowerCase() === selector.toLowerCase();
}
return matchFunction;
};
Thanks in advance!

The Array is empty - foreach() does not seam to work || The values does not get passed into the loop

const filteredOptions = _.filter(grantedValuesFinal, o => o.numberOfShareOptionAwardGranted > 0);
let filteredArray = [];
filteredOptions.forEach(function(item, i){
let existing = filteredArray.filter(function(o, i) {
const dateNew = luxon.DateTime.fromISO(o.grantDate);
const dateExsisting = luxon.DateTime.fromISO(item.grantDate);
return o.grantDate == item.grantDate;
});
if (existing.length) {
let existingIndex = filteredArray.indexOf(existing[0]);
if(item.typeOfEquity == "Shares") filteredArray[existingIndex].numberOfShareOptionAwardGrantedShares += item.numberOfShareOptionAwardGranted;
if(item.typeOfEquity == "Options") filteredArray[existingIndex].numberOfShareOptionAwardGrantedOptions += item.numberOfShareOptionAwardGranted;
} else {
let numberOfShareOptionAwardGrantedShares = 0, numberOfShareOptionAwardGrantedOptions = 0;
if(item.typeOfEquity == "Shares") numberOfShareOptionAwardGrantedShares += item.numberOfShareOptionAwardGranted;
if(item.typeOfEquity == "Options") numberOfShareOptionAwardGrantedOptions += item.numberOfShareOptionAwardGranted;
filteredArray.push({grantDate: item.grantDate, numberOfShareOptionAwardGrantedShares: numberOfShareOptionAwardGrantedShares, numberOfShareOptionAwardGrantedOptions: numberOfShareOptionAwardGrantedOptions });
}
})
The very first line of the code does not pass any value, due to which filteredOptions and array is empty. What changes has to be done ? Please help
The filteredOptions array always stayed NULL as the grantedValuesFinal was only filtering out NULL values.
I was able to work out the code by placing !=NULL and got the non-null values.
Thank you all for your insights

Can't break out of some() array method

I got a click event attached to a button to perform a search that checks if a certain element matches a certain condition. In the snippet below there is a some() array method that checks the 'entriesFound' array for the element that matches a certain condition. However everything works find till the else if(el.name !== name.value) condition. The alertbox shows but I need to click the OK button in the alertbox as many times as there are elements in the entriesFound array.
import { persons } from './main.js';
export let entriesFound = []
export const searchBtn = document.querySelector('.search').addEventListener('click' , function() {
let name = document.querySelector('.searchInput')
if(name.value === "") {
alert('No search query!')
return;
}
entriesFound.some( el => {
if(el.name === name.value){
name.value = ""
alert("You\'ve already found what you are looking for!")
el.remove();
// from here things go wrong
}else if(el.name !== name.value){
alert("No data found!")
return;
}
})
persons.some( el => {
if(el.name === name.value) {
addItem(el)
entriesFound.push(el);
}
})
name.value = ""
localStorage.setItem('entriesFound', JSON.stringify(entriesFound))
})
You should use the return value of some and you can utilize find:
import { persons } from "./main.js";
export let entriesFound = [];
export const searchBtn = document
.querySelector(".search")
.addEventListener("click", function () {
let name = document.querySelector(".searchInput");
if (name.value === "") {
alert("No search query!");
return;
}
const entryExists = entriesFound.some((el) => el.name === name.value);
if (entryExists) {
name.value = "";
alert("You've already found what you are looking for!");
el.remove();
// from here things go wrong
} else {
alert("No data found!");
return;
}
const item = persons.find(el.name === name.value);
if (item !== null) {
addItem(item);
entriesFound.push(item);
}
name.value = "";
localStorage.setItem("entriesFound", JSON.stringify(entriesFound));
});
Thanks all of you. I combined your solutions. I used regular for loops and the every() array method.
import { persons } from "./main.js";
export let entriesFound = [];
export const searchBtn =
document.querySelector('.search').addEventListener('click' , () => {
let name = document.querySelector('.searchInput')
if(name.value === "") {
alert('No search query!')
return;
}
if(entriesFound.length > 0){
for(let el of entriesFound){
if(el.name === name.value) {
alert('You have found that one already!')
name.value = ""
return;
}
}
}
if(persons.length > 0 ){
for(let el of persons) {
if(el.name === name.value) {
addItem(el)
entriesFound.push(el)
}
}
}else if(persons.length === 0 ){
alert('No data!')
name.value = ""
return;
}
let noMatch = persons.every( el => el.name !== name.value)
console.log(noMatch)
if(noMatch === true){
alert('No match!');
name.value = ""
return;
}
name.value = ""
localStorage.setItem('entriesFound', JSON.stringify(entriesFound))
});

Removing specific item from localStorage

I'm adding some items to localStorage, using jQuery/JS, which is all fine but in trying to remove a specific item within the array (if it's the same item) is proving difficult.
In my console logs (for testing) it seems to clear the [Object] but it's not updating the key. Perhaps my hierarchy is wrong... any ideas?
//
function addToStorage(elem, name) {
localData = localStorage.getItem(name + 'Storage');
var typefaces;
if (localData == 'null' || !localData) {
typefaces = [];
} else {
typefaces = JSON.parse(localData);
}
typefaceID = elem.find('input').val();
typefaceName = elem.find('input').attr('data-name');
typefacePrice = elem.find('input').attr('data-price');
typefaceQty = 1;
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
//console.log(index);
//console.log(typefaces);
typefaces.splice(index, 1);
//console.log(typefaces);
}
}
}
});
typefaces.push({
'id': typefaceID,
'name': typefaceName,
'price': typefacePrice,
'qty': typefaceQty
});
localStorage.setItem(name + 'Storage', JSON.stringify(typefaces));
}
//
$(document).on('click', 'summary.cart td.font', function(e) {
e.preventDefault();
addTo = $(this);
addTo.each(function() {
addToStorage(addTo, 'drf');
});
});
This is an example of the localData once it's been added to.
[
{
"id":"dr-raymond-desktop-40374",
"name":"DR-Raymond",
"format":"Desktop (OTF)",
"price":"15.00",
"qty":1
},
{
"id":"dr-raymond-webfont-39949",
"name":"DR-Raymond",
"format":"Webfont (WOFF)",
"price":"15.00",
"qty":1
}
]
Never add/remove elements from an array while iterating over it using "foreach". Instead, try this:
for (index = typefaces.length - 1; index >= 0; index--){
value = typefaces[index];
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
typefaces.splice(index, 1);
}
}
}
});
Another way to do this more elegantly is by using ES6 filter():
typefaces = typefaces.filter(value => !(value && value.id == typefaceID && name == "drf"));
Also, you are comparing localData to the literal string 'null' which is kind of pointless. Your second condition - if (!localData) is enough in this case, and will handle it properly.
The problem lies in your splice method usage. Note that, according to MDN splice modifies the array in place and returns a new array containing the elements that have been removed. So when using a loop and trying to remove the some elements, splice will make shifting of elements. This is because iterating incrementally through the array, when you splice it, the array is modified in place, so the items are "shifted" and you end up skipping the iteration of some. Looping backwards fixes this because you're not looping in the direction you're splicing.
Solution 1
Loop backwards while splicing.
for(var i = typefaces.length; i--;)
{
if (typefaces[i] !== null)
{
if (typefaces[i] == typefaceID)
{
typefaces.splice(i, 1);
}
}
}
Working bin link here.
Solution 2
Its usually faster to generate a new array instead of modifying the existing one. So, your code will look like
ty = [];
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id != typefaceID) {
ty.push(value);
}
}
});
typefaces = ty;
Working bin link is here.
After that there is no problem found in getting and setting your localStorage.
You have an additional error in your code, you write
if (value !== null) {
if (value.id == typefaceID) {
// name will always have the value drf
// since you pass it in your function
// when you call it addToStorage(addTo, 'drf');
if (name == 'drf') {
typefaces.splice(index, 1);
}
}
}
when it should be
if (value !== null) {
if (value.id == typefaceID) {
// here the comparison should be between value.name and name
if (value.name == name) {
typefaces.splice(index, 1);
}
}
}
which can be also written
if (value !== null) {
if (value.id == typefaceID && value.name == name) {
typefaces.splice(index, 1);
}
}
You are mutating the array by splicing it. The only localstorage items that are being re written are the ones that are left in the array.
So delete the item from the storage when you splice an object from the array.
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
//console.log(index);
//console.log(typefaces);
var removedTypeFace = typefaces.splice(index, 1);
//console.log(typefaces);
if(removedTypeFace) {
localStorage.removeItem(removedTypeFace.name + 'Storage');
}
}
}
}
});
OK. You're going to kick yourself. The first time you pull from localStorage, the getItem method DOES return a null value, but you're trying to compare it to a string ('null', in quotes). Fail. I changed localData == 'null' to localData == null.
The second part of the expression tries to see the value returned from getItem is actually defined, which is good. I changed that to be more explicit, by way of typeof.
Explicit variable declarations help, too.
function addToStorage(elem, name) {
var localData = localStorage.getItem(name + 'Storage');
var typefaces;
if (localData == null || typeof(localData) == 'undefined') {
typefaces = [];
} else {
typefaces = JSON.parse(localData);
}
var typefaceID = elem.find('input').val();
var typefaceName = elem.find('input').attr('data-name');
var typefacePrice = elem.find('input').attr('data-price');
var typefaceQty = 1;
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
//console.log(index);
//console.log(typefaces);
typefaces.splice(index, 1);
//console.log(typefaces);
}
}
}
});
typefaces.push({
'id': typefaceID,
'name': typefaceName,
'price': typefacePrice,
'qty': typefaceQty
});
localStorage.setItem(name + 'Storage', JSON.stringify(typefaces));
}
//
jQuery(document).on('click', 'summary.cart td.font', function(e) {
e.preventDefault();
addTo = $(this);
addTo.each(function() {
addToStorage(addTo, 'drf');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<summary class='cart'><table><tr>
<td class='font' >Click Here for Item 1
<input data-name='abc' data-price='2.00' value="Item 1" /></td></tr>
<tr>
<td class='font' >Click Here for Item 2
<input data-name='def' data-price='4.00' value="Item 2" /></td></tr></table>
</summary>

Categories