How to optimize a nested 'for' to functional programming - javascript

I created a function to check if there are repeated cell phone numbers in a list. The problem is that I did this by using nested for. How could I optimize this code with functional programming ?
checkDuplicate(): boolean {
for (let i = 0; i < this.phoneList.length; i++) {
for (let j = 0; j < this.phoneList.length; j++) {
if (i != j) {
if (this.phoneList[i].number === this.phoneList[j].number) {
this.toastrService.error('Phone already in List!');
return true;
}
}
}
}
return false;
}

You can make a Set containing only the unique numbers and compare the length of the Set to the length of the original array
hasDuplicates(): boolean {
return new Set(this.phoneList.map(p => p.number)).size < this.phoneList.length
}

O(n) solution
It's not a functional but it's the fastest so far.
const checkDuplicate = (phones)=> {
let counts = {};
for(let phone of phones) {
if(counts[phone.number]) return true;
counts[phone.number] = 1;
}
return false;
}
if(checkDuplicate(this.phoneList)) {
this.toastrService.error('Phone already in List!');
}

even better than filter (which i suggested in a comment) use Set - there are a few ways to do it but this is pretty clean. However .filter() would probably be considered more 'functional' as it is a HOC
let a = [1,2,1,3,3,5]
let x = [...new Set(a)]
// => [1, 2, 3, 5]
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

You could do something like:
let dups = this.phoneList.filter(item =>
this.phoneList.filter(item2 => item.number == item2.number).length > 1
);
if (dups.length) {
this.toastrService.error('Phone already in List!');
return true;
}
...though it suffers a little for readability.

It's not really about angular but just the JavaScript. You could just short cycle the loop on the list as faster.
Each inner loop is n-i faster (less to do/check) since we already checked those
var xObj = {
phoneList: [{
name: "freddy",
number: 55512121234
}, {
name: "Jimmy",
number: 55512121234
}, {
name: "Mommy",
number: 55512121233
},
{
name: "Tommy",
number: 55512121244
},
{
name: "Luka",
number: 55512121222
},
{
name: "Penny",
number: 55512121255
},
{
name: "Sammy",
number: 55512121266
},
{
name: "Bill",
number: 55512121244
}
],
phoneList2: [{
name: "freddy",
number: 55512121234
}, {
name: "Jimmy",
number: 55512121235
}, {
name: "Mommy",
number: 55512121233
},
{
name: "Tommy",
number: 55512121244
},
{
name: "Luka",
number: 55512121222
},
{
name: "Penny",
number: 55512121259
},
{
name: "Sammy",
number: 55512121266
},
{
name: "Bill",
number: 55512121247
}
],
toastrService: {
error: function(message) {
console.log(message);
}
},
checkDuplicate: function() {
let hasDupe = false
for (let i = 0; i < this.phoneList.length; i++) {
for (let j = i + 1; j < this.phoneList.length; j++) {
if (this.phoneList[i].number === this.phoneList[j].number) {
hasDupe = true;
break;
}
}
if (hasDupe) break;
}
if (hasDupe) this.toastrService.error('Phone already in List!');
return hasDupe;
},
checkDuplicate2: function() {
let hasDupe = false
for (let i = 0; i < this.phoneList2.length; i++) {
for (let j = i + 1; j < this.phoneList2.length; j++) {
if (this.phoneList2[i].number === this.phoneList2[j].number) {
hasDupe = true;
break;
}
}
if (hasDupe) break;
}
if (hasDupe) this.toastrService.error('Phone already in List!');
return hasDupe;
}
};
let cdup = xObj.checkDuplicate();
let cdup2 = xObj.checkDuplicate2();
console.log("dup:", cdup, cdup2);

You can use Array.some to check if a phone number is a duplicate, as shown below. In the loop callback, the phone number is the key of a boolean value added to the exists object. The loop stops as soon as the callback function returns true, which happens when a key/value corresponding to the loop item is found in exists.
checkDuplicate(): boolean {
let exists: { [key: number]: boolean } = {};
return this.phoneList.some(phoneListItem => {
if (exists[phoneListItem.number]) {
return true;
} else {
exists[phoneListItem.number] = true;
return false;
}
});
}
See this stackblitz for a demo.

Related

Find Duplicate Array By Caption without using multiple loops [duplicate]

I need some help with iterating through array, I keep getting stuck or reinventing the wheel.
values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName1' },
{ name: 'someName1' }
]
How could I check if there are two (or more) same name value in array? I do not need a counter, just setting some variable if array values are not unique. Have in mind that array length is dynamic, also array values.
Use array.prototype.map and array.prototype.some:
var values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName4' },
{ name: 'someName2' }
];
var valueArr = values.map(function(item){ return item.name });
var isDuplicate = valueArr.some(function(item, idx){
return valueArr.indexOf(item) != idx
});
console.log(isDuplicate);
ECMA Script 6 Version
If you are in an environment which supports ECMA Script 6's Set, then you can use Array.prototype.some and a Set object, like this
let seen = new Set();
var hasDuplicates = values.some(function(currentObject) {
return seen.size === seen.add(currentObject.name).size;
});
Here, we insert each and every object's name into the Set and we check if the size before and after adding are the same. This works because Set.size returns a number based on unique data (set only adds entries if the data is unique). If/when you have duplicate names, the size won't increase (because the data won't be unique) which means that we would have already seen the current name and it will return true.
ECMA Script 5 Version
If you don't have Set support, then you can use a normal JavaScript object itself, like this
var seen = {};
var hasDuplicates = values.some(function(currentObject) {
if (seen.hasOwnProperty(currentObject.name)) {
// Current name is already seen
return true;
}
// Current name is being seen for the first time
return (seen[currentObject.name] = false);
});
The same can be written succinctly, like this
var seen = {};
var hasDuplicates = values.some(function (currentObject) {
return seen.hasOwnProperty(currentObject.name)
|| (seen[currentObject.name] = false);
});
Note: In both the cases, we use Array.prototype.some because it will short-circuit. The moment it gets a truthy value from the function, it will return true immediately, it will not process rest of the elements.
In TS and ES6 you can create a new Set with the property to be unique and compare it's size to the original array.
const values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName3' },
{ name: 'someName1' }
]
const uniqueValues = new Set(values.map(v => v.name));
if (uniqueValues.size < values.length) {
console.log('duplicates found')
}
To know if simple array has duplicates we can compare first and last indexes of the same value:
The function:
var hasDupsSimple = function(array) {
return array.some(function(value) { // .some will break as soon as duplicate found (no need to itterate over all array)
return array.indexOf(value) !== array.lastIndexOf(value); // comparing first and last indexes of the same value
})
}
Tests:
hasDupsSimple([1,2,3,4,2,7])
// => true
hasDupsSimple([1,2,3,4,8,7])
// => false
hasDupsSimple([1,"hello",3,"bye","hello",7])
// => true
For an array of objects we need to convert the objects values to a simple array first:
Converting array of objects to the simple array with map:
var hasDupsObjects = function(array) {
return array.map(function(value) {
return value.suit + value.rank
}).some(function(value, index, array) {
return array.indexOf(value) !== array.lastIndexOf(value);
})
}
Tests:
var cardHand = [
{ "suit":"spades", "rank":"ten" },
{ "suit":"diamonds", "rank":"ace" },
{ "suit":"hearts", "rank":"ten" },
{ "suit":"clubs", "rank":"two" },
{ "suit":"spades", "rank":"three" },
]
hasDupsObjects(cardHand);
// => false
var cardHand2 = [
{ "suit":"spades", "rank":"ten" },
{ "suit":"diamonds", "rank":"ace" },
{ "suit":"hearts", "rank":"ten" },
{ "suit":"clubs", "rank":"two" },
{ "suit":"spades", "rank":"ten" },
]
hasDupsObjects(cardHand2);
// => true
if you are looking for a boolean, the quickest way would be
var values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName1' },
{ name: 'someName1' }
]
// solution
var hasDuplicate = false;
values.map(v => v.name).sort().sort((a, b) => {
if (a === b) hasDuplicate = true
})
console.log('hasDuplicate', hasDuplicate)
const values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName4' },
{ name: 'someName4' }
];
const foundDuplicateName = values.find((nnn, index) =>{
return values.find((x, ind)=> x.name === nnn.name && index !== ind )
})
console.log(foundDuplicateName)
Found the first one duplicate name
const values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName4' },
{ name: 'someName4' }
];
const foundDuplicateName = values.find((nnn, index) =>{
return values.find((x, ind)=> x.name === nnn.name && index !== ind )
})
You just need one line of code.
var values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName4' },
{ name: 'someName2' }
];
let hasDuplicates = values.map(v => v.name).length > new Set(values.map(v => v.name)).size ? true : false;
Try an simple loop:
var repeat = [], tmp, i = 0;
while(i < values.length){
repeat.indexOf(tmp = values[i++].name) > -1 ? values.pop(i--) : repeat.push(tmp)
}
Demo
With Underscore.js A few ways with Underscore can be done. Here is one of them. Checking if the array is already unique.
function isNameUnique(values){
return _.uniq(values, function(v){ return v.name }).length == values.length
}
With vanilla JavaScript
By checking if there is no recurring names in the array.
function isNameUnique(values){
var names = values.map(function(v){ return v.name });
return !names.some(function(v){
return names.filter(function(w){ return w==v }).length>1
});
}
//checking duplicate elements in an array
var arr=[1,3,4,6,8,9,1,3,4,7];
var hp=new Map();
console.log(arr.sort());
var freq=0;
for(var i=1;i<arr.length;i++){
// console.log(arr[i-1]+" "+arr[i]);
if(arr[i]==arr[i-1]){
freq++;
}
else{
hp.set(arr[i-1],freq+1);
freq=0;
}
}
console.log(hp);
You can use map to return just the name, and then use this forEach trick to check if it exists at least twice:
var areAnyDuplicates = false;
values.map(function(obj) {
return obj.name;
}).forEach(function (element, index, arr) {
if (arr.indexOf(element) !== index) {
areAnyDuplicates = true;
}
});
Fiddle
Adding updated es6 function to check for unique and duplicate values in array. This function is modular and can be reused throughout the code base. Thanks to all the post above.
/* checks for unique keynames in array */
const checkForUnique = (arrToCheck, keyName) => {
/* make set to remove duplicates and compare to */
const uniqueValues = [...new Set(arrToCheck.map(v => v[keyName]))];
if(arrToCheck.length !== uniqueValues.length){
console.log('NOT UNIQUE')
return false
}
return true
}
let arr = [{name:'joshua'},{name:'tony'},{name:'joshua'}]
/* call function with arr and key to check for */
let isUnique = checkForUnique(arr,'name')
checkDuplicate(arr, item) {
const uniqueValues = new Set(arr.map((v) => v[item]));
return uniqueValues.size < arr.length;
},
console.log(this.checkDuplicate(this.dutyExemptionBase, 'CI_ExemptionType')); // true || false
It is quite interesting to work with arrays
You can use new Set() method to find duplicate values!
let's assume you have an array of objects like this...
let myArray = [
{ id: 0, name: "Jhon" },
{ id: 1, name: "sara" },
{ id: 2, name: "pop" },
{ id: 3, name: "sara" }
]
const findUnique = new Set(myArray.map(x => {
return x.name
}))
if(findUnique.size < myArray.length){
console.log("duplicates found!")
}else{
console.log("Done!")
}
const duplicateValues = [{ name: "abc" }, { name: "bcv" }, { name: "abc" }];
const isContainDuplicate = (params) => {
const removedDuplicate = new Set(params.map((el) => el.name));
return params.length !== removedDuplicate.size;
};
const isDuplicate = isContainDuplicate(duplicateValues);
console.log("isDuplicate");

how to use .include() method to check the value which is in a json inside array

I want to compare the value of a particular key in my JSON array with new value to check whether the value exists or not.
For example, I have an array:
[
{ name: abc, num: 121212 },
{ name: bcd, num: 21212 },
{ name: def, num: 111222 }
]
Now a new value comes which I want to check. Does that name already exist? If it does, then I only want to update the number and if not then I want to push the object in the array.
Here is my code:
if ((Dnum.num).includes(number)) {
console.log("inside if");
console.log(Dnum.indexOf(number));
} else {
Dnum.push({num:number,
lat:lat,
lng:lng,
name:name
});
}
Well, your problem (if I understand correctly) is that you want to use includes() but what you actually want to accomplish doesn't correspond to what the method does. You want to find if there's an object with a certain name in your array already, not if it contains a known element. Something like this:
var data = [{name: 'abc', num: 121212}, {name: 'bcd', num: 21212}, {name: 'def', num: 111222}];
function addOrUpdate(newElement, data) {
var i;
for (i = 0; i < data.length; i++) {
if (data[i].name == newElement.name) {
data[i] = newElement;
return;
}
}
data.push(newElement);
}
addOrUpdate({name: 'bcd', num: 131313}, data);
console.log(data);
addOrUpdate({name: 'new', num: 131313}, data);
console.log(data);
Problem:
Actually .includes() and .indexOf() methods won't work with objects, they should be used with an array of strings or Numbers as they use strict equality to compare the elements and objects can't be compared this way, so you need to implement this logic by yourself.
Solution:
You need to check if an object matching the searched name already exists in the array, update the num value of this object, otherwise if no object matches the searched name, push the new object to the array:
if (arr.some(function(obj) {
return obj.name === searchedVal.name;
})) {
arr.forEach(function(el, index) {
if (el.name === searchedVal.name) {
el.num += searchedVal.num;
found = true;
}
});
} else {
arr.push(searchedVal);
}
Demo:
var arr = [{
name: "abc",
num: 121212
}, {
name: "bcd",
num: 21212
}, {
name: "def",
num: 111222
}];
var searchedVal = {
name: "abc",
num: 5
};
if (arr.some(function(obj) {
return obj.name === searchedVal.name;
})) {
arr.forEach(function(el, index) {
if (el.name === searchedVal.name) {
el.num += searchedVal.num;
found = true;
}
});
} else {
arr.push(searchedVal);
}
console.log(arr);
If you don't want to use .some() method, you can do it this way:
var searchedVal = {
name: "abc",
num: 5
};
var found = false;
arr.forEach(function(el, index) {
if (el.name === searchedVal.name) {
el.num+= searchedVal.num;
found = true;
}
});
if (!found) {
arr.push(searchedVal);
}
Use Array.prototype.find():
var res = Dnum.find(function (item) {
return item.num === number;
});
if (res) {
console.log("inside if");
console.log(res);
res.num = number;
} else {
Dnum.push({
num:number,
lat:lat,
lng:lng,
name:name
});
}

Input Multiple Var to Function, Search JSON Array, and Receive Multiple Array Val as Output

I have a json array, in a javascript function. The function takes input, searches the array for key/val, and gives me the value I need.
What I am trying to accomplish is input a string of variables like:
1,2,3,4,5
Into this function:
function getF(fid) {
var flist = [
{
"fintid":1,
"featureint":"See Remarks"
},
{
"fintid":2,
"featureint":"Accessory Apt"
},
{
"fintid":3,
"featureint":"Alarm: Fire"
},
{
"fintid":4,
"featureint":"Alarm: Security"
},
{
"fintid":5,
"featureint":"Bar: Dry"
}
]
for(var i = 0; i < flist.length; i++)
{
if(flist[i].fintid == fid) {
return flist[i].featureint;
}
}
}
And receive all corresponding "featureint" values, that match with all fid/fintid given to the function. I can do this if I give only one number. I want to give multiple numbers, and receive multiple values... Please help? :)
Assuming the past in value is an array (not a string delimited by , - if it is, split it to get an array) - you can use indexOf to check the looped over object ID against the array. Push all matches to an array, then return:
function getF(fid) {
//Split and cast to array of numbers
fid = fid.split(",").map(function(i) { return parseInt(i) });
var flist = [
{
"fintid":1,
"featureint":"See Remarks"
},
{
"fintid":2,
"featureint":"Accessory Apt"
}
..
]
var matches = [];
for(var i = 0; i < flist.length; i++) {
if(fid.indexOf(flist[i].fintid) > -1) {
matches.push(flist[i].featureint);
}
}
return matches;
}
Sample usage:
getF("1,2");
Demo: https://jsfiddle.net/5p4wmjyu/
As a variant, if you don't know how many parameters you have, you can use apply function
function getF() {
var flist = [{
"fintid": 1,
"featureint": "See Remarks"
}, {
"fintid": 2,
"featureint": "Accessory Apt"
}, {
"fintid": 3,
"featureint": "Alarm: Fire"
}, {
"fintid": 4,
"featureint": "Alarm: Security"
}, {
"fintid": 5,
"featureint": "Bar: Dry"
}];
var newArray = [];
var args = [].slice.call(arguments);
for (var i = 0; i < flist.length; i++) {
if (args.indexOf(flist[i].fintid) > -1) {
newArray.push(flist[i].featureint);
}
}
return newArray;
}
console.log(getF.apply(null, [2, 5]));
You can slice the arguments into an array, then use indexOf as others have suggested. Note the result in this case is an array of featureint values, as that's what I understand you're after.
function getF(fid) {
var flist = [
{
"fintid":1,
"featureint":"See Remarks"
},
{
"fintid":2,
"featureint":"Accessory Apt"
},
{
"fintid":3,
"featureint":"Alarm: Fire"
},
{
"fintid":4,
"featureint":"Alarm: Security"
},
{
"fintid":5,
"featureint":"Bar: Dry"
}
];
var args = Array.prototype.slice.call(arguments);
return flist.reduce(function(matches, current) {
if(args.indexOf(current.fintid) > -1) {
matches.push(current.featureint);
}
return matches;
}, []);
}
var result = getF(1, 3, 4);
console.log(result);
var stringArg = "1, 2, 5";
//apply to pass an array in as args, split to make the array from the string, then map to convert from string to number
result = getF.apply(this, stringArg.split(',').map(function(x){ return Number(x.trim())}));
console.log(result);
You can use Array.filter() to accomplish this
var flist = [
{
"fintid":1,
"featureint":"See Remarks"
},
{
"fintid":2,
"featureint":"Accessory Apt"
},
{
"fintid":3,
"featureint":"Alarm: Fire"
},
{
"fintid":4,
"featureint":"Alarm: Security"
},
{
"fintid":5,
"featureint":"Bar: Dry"
}
]
var searchInt = 3;
alert(flist.filter(function(v){
if(v['fintid']===searchInt){
return true;
}
return false;
},searchInt)[0]['featureint']);
//FOR ARRAY INPUT
var input = [1,2,3];
alert(input.map(function(val){
return flist.filter(function(v){
if(v['fintid']===val){
return true;
}
return false;
},val)[0]['featureint'];
}).toString());

Get string representation of JavaScript object

I have a object like so:
$scope.query = {
filter: {
column: {
productName: 'Some Product',
price: 29.95,
...
},
table: {
productType: 'GM',
categoryId: 1,
...
}
}
};
How do I get a string that represents the whole object in dot notation? e.g.
query.filter.table.productType
To clarify, I am using this string value as a key to store a key/value pair in localStorage.
I am using angular to $wacth each property on the object for a change. Since you can't watch an object and know which property changed with watching all, I need to get creative and store each property in a key/value pair.
You can do it recursively, and produces "key" in an array.
var obj = {
query: {
filter: {
table: {
productType: 'GM'
}
}
}
};
var stringify = function (e) {
var rs = [];
for (var k in e) {
if (e.hasOwnProperty(k)) {
if (typeof e[k] == 'object') {
var l = stringify(e[k]);
for (var i = 0; i < l.length; i++) {
rs.push(k + '.' + l[i]);
}
} else {
rs.push(k);
}
}
}
return rs;
}
console.log(stringify(obj));
outputs:
["query.filter.table.productType"]
fiddle
Demo
Before Ques Edit
var $scope = {
query: {
filter: {
table: {
productType: 'GM'
}
}
}
};
var k = JSON.stringify($scope)
//output "{"query":{"filter":{"table":{"productType":"GM"}}}}"
k.match(/\w+(?=\"\:)/g).join('.')
//output"query.filter.table.productType"
Edit
Updated Demo
If OP has no issue with the position of child elements
var $scope = {}
$scope.query = {
filter: {
column: {
productName: 'Some Product',
price: 29.95
},
table: {
productType: 'GM',
categoryId: 1,
}
}
};
k=JSON.stringify($scope)
{"query":{"filter":{"column":{"productName":"Some Product","price":29.95},"table":{"productType":"GM","categoryId":1}}}}
k.match(/\w+(?=\"\:)/g).join('.')
"query.filter.column.productName.price.table.productType.categoryId"
By iterating the properties into an array recursively you could create a hierarchical structure that represents the data in the object. From here you could parse the results out as you wish.
var scope = {
query: {
filter: {
column: {
productName: 'Some Product',
price: 29.95
},
table: {
productType: 'GM',
categoryId: 1
}
}
}
};
function buildProps(subject) {
var result = [];
for (var key in subject) {
if (subject.hasOwnProperty(key)) {
if (typeof subject[key] == "object") {
result.push(key, buildProps(subject[key]));
} else {
result.push(key);
}
}
}
return result;
}
function stringify(input) {
var result = [];
for (var i = 0; i < input.length; i++) {
if (typeof input[i] == "string") {
result.push(input[i]);
} else {
result = result.concat(stringify(input[i]));
}
}
return result.join('.');
}
console.log(buildProps(scope));
console.log(stringify(buildProps(scope)));
Parse out the strings in the resulting array/sub-arrays, format it any way you like.
In my simple example I just list them in order:
query.filter.column.productName.price.table.productType.categoryId

javascript find an object with specific properties in an array

I need to make an extension to existing code, can't change it.
There's this array:
var availableTags = [
{ label: "Yoga classes", category: "EDUCATIONAL" },
{ label: "Cooking classes", category: "EDUCATIONAL" },
{ label: "Cheese tastings", category: "EDUCATIONAL" },
{ label: "Maker Workshops", category: "PRACTICAL" },
{ label: "Seminars", category: "PRACTICAL" },
//many more of these
];
Now I need to check if a text entered in an input box is included in one of the labels, e.g. if the user enters "Yoga classes" => OK, if "Yoga" => NOK, "sdsdf" => NOK, etc.
What is the best way to do this? I am not sure I can use Array.indexOf as I am not sure how to pass the Object to the function, I would try looping through the array (around 40 entries) and compare each object.
You need to loop over every item in availableTags and check whether that item's label is equal to some input. Try something like this:
var input = "Yoga classes";
var found = false;
for (var i = 0, j = availableTags.length; i < j; i++) {
var cur = availableTags[i];
if (cur.label === input) {
found = true;
break;
}
}
console.log(found);
DEMO: http://jsfiddle.net/k4cp4/4/
Where this can easily be put into a function, like:
var checkMatch = (function () {
var availableTags = [
{ label: "Yoga classes", category: "EDUCATIONAL" },
{ label: "Cooking classes", category: "EDUCATIONAL" },
{ label: "Cheese tastings", category: "EDUCATIONAL" },
{ label: "Maker Workshops", category: "PRACTICAL" },
{ label: "Seminars", category: "PRACTICAL" }
];
return function (input) {
var found = false;
for (var i = 0, j = availableTags.length; i < j; i++) {
var cur = availableTags[i];
if (cur.label === input) {
found = true;
break;
}
}
return found;
};
})();
DEMO: http://jsfiddle.net/k4cp4/5/
This checks for an exact match. So if you want a case insensitive match, you can use:
if (cur.label.toLowerCase() === input.toLowerCase()) {
DEMO: http://jsfiddle.net/k4cp4/6/
If you want to see if any of the labels contain the input, you can use indexOf like:
if (cur.label.indexOf(input) > -1) {
DEMO: http://jsfiddle.net/k4cp4/7/
You can use Array.some method:
Tests whether some element in the array passes the test implemented by the provided function.
Then your code would look something like:
var isFound = availableTags.some(function(el) {
return el.label === 'Yoga classes';
});
Note: some method needs to be shimmed.
var check = function(item) {
for(at in availableTags) {
if(item == availableTags[at].label) {
return true;
}
}
return false;
}
console.log(check("Yoga classes"));

Categories