How can I make this function more efficient? - javascript

I have this data structure of credit card types.
It would be nice to make the hasTransFee more efficient. If I started adding storecards etc to this list it could get quite big so the faster it works the better.
Anyone have any suggestions?
$scope.creditCards = [
{ name: 'VISA DEBIT/DELTA', value: 'DEL', transactionFee: false},
{ name: 'VISA CREDIT', value: 'VIS', transactionFee: true },
{ name: 'MASTERCARD CREDIT', value: 'MSC', transactionFee: true },
{ name: 'MASTERCARD DEBIT', value: 'MCD', transactionFee: false },
{ name: 'MAESTRO', value: 'MAE', transactionFee: false },
{ name: 'SWITCH', value: 'SWI', transactionFee: false },
{ name: 'VISA ELECTRON', value: 'ELC', transactionFee: false },
{ name: 'SOLO', value: 'SOL', transactionFee: false }
];
var hasTransFee = function(cardType)
{
for (var i=0; i < $scope.creditCards.length; i++) {
if($scope.creditCards[i].value==cardType && $scope.creditCards[i].transactionFee == true){
return true;
}
}
return false;
}

It seems that value is a unique identifier, if that's the case you could store the "credit cards" in an object instead, like this:
$scope.creditCards = {
'DEL': { name: 'VISA DEBIT/DELTA', transactionFee: false},
'VIS': { name: 'VISA CREDIT', transactionFee: true },
'MSC': { name: 'MASTERCARD CREDIT', transactionFee: true }
};
And then you don't even need a function for checking if the Credit Card has a transactionFee, if you still want to have a function, that function would look like this:
var hasTransFee = function(cardType){
return $scope.creditCards[cardType].transactionFee;
}

Side-note: This is better suited for CodeReview
Try this:
var hasTransFee = function(cardType)
{
var thecard;
$scope.creditCards.some(function(item) {
return item.value == cardType && (thecard = item);
});
return thecard && thecard.transactionFee;
};
This will only iterate as far as it needs to in order to find the card with the right type, and no further (your original code will continue to scan the whole array if the card is found but has no fee).
Important: Only ONE = sign in && thecard = item. It is deliberately an assignment, required to make the final return work.

Related

How do I preserve the order of this javascript array sort?

const modules = [
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
]
I am trying to sort the array so that the True values come first , then False values.
const orderedModules = modules.sort((a, b) => (a.checked ? -1 : 1))
However, I'd like to preserve the order of the True values. The code above sometimes puts Air first, then Earth (if ran twice). How can I preserve the order all the time?
The callback used as the compare function can return 3 options: negative number, positive number or zero. The negative number indicates that the first parameter should be before the second parameter in the array order. The positive number indicates that the first parameter should be after the second parameter in the array order. And zero means that the order should be kept as is.
Sort array method on MDN
If you want to order just the true values first in the same order in the array and then the false values, probably adding more logic to return zero from the compare function if both are true will solve your issue.
Here is an example:
const modules = [
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
];
modules.sort((a,b) => a.checked && b.checked ? 0 : a.checked ? -1 : 1);
console.log(modules);
If you don't mind creating a new array, just iterate over the array twice. The first time, push to the new array the objects with the true values as the iteration encounters them. The second time, push the objects with the false values. (JavaScript passes objects by reference so the new array won't cause them to get duplicated.)
Probably this might help you. not sure for optimize way but this function iterate over an array one time only.
I am using reduce function to separate out true and false values and then return them in the order you want.
const shortItems = (array) => {
const orderedModulesObject = array.reduce((orderedModulesObject, currentModule) => {
if(currentModule.checked){
orderedModulesObject.trueValues = orderedModulesObject.trueValues.concat(currentModule);
} else {
orderedModulesObject.falseValues = orderedModulesObject.falseValues.concat(currentModule);
}
return orderedModulesObject;
}, { trueValues: [], falseValues: []});
return orderedModulesObject.trueValues.concat(orderedModulesObject.falseValues);
}
const modules = [
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
]
console.log(shortItems(modules));
the reason is that sort actually changes the original array. Although modules is defined with const, the values inside can change, as long as you don't assign the variable to something else.
according to this answer, you can sort without mutating the original using spread syntax. this code should work:
const orderedModules = [...modules].sort((a, b) => (a.checked ? -1 : 1))
to make an array or object not able to be modified, you can use Object.freeze()
const modules = Object.freeze([
{ name: 'Wood', checked: false },
{ name: 'Metal', checked: false },
{ name: 'Earth', checked: true },
{ name: 'Water', checked: false },
{ name: 'Air', checked: true },
{ name: 'Fire', checked: false },
])
Edit: I just realized the order isn't correct, but it at least is the same every time. but that's because the sorting isn't exactly right. here's the correct code:
const orderedModules = [...modules].sort((a, b) => (a.checked != b.checked ? (a.checked ? -1 : 1 ) : 0))

JavaScript Recursive Search On An Array Of Objects

I have an array of objects that have deeply nested children and sometimes children within children. I am attempting to handle this recursively, but I am getting stuck.
The goal of the function is to return a single data object that matches the id.
My Data looks like this:
data: [
{
id: 'RAKUFNUBNY00UBZ40950',
name: 'Grade 1 Cover',
activityId: 'RAKUFNUBNY00UBZ40950',
nodeType: 'activity',
suppressed: false,
hidden: false
},
{
children: [
{
id: 'SLWDYEQHTZAFA3ALH195',
name: 'Build Background Video',
activityId: 'SLWDYEQHTZAFA3ALH195',
nodeType: 'activity',
suppressed: false,
hidden: false,
assetReference: {
referenceId: 'UWFHA5A1E0EGKCM0W899',
assetType: 'image'
}
},
{
children: [
{
id: 'HQUCD2SSRKMYC2PJM636',
name: 'Eat or Be Eaten Splash Card',
activityId: 'HQUCD2SSRKMYC2PJM636',
nodeType: 'activity',
suppressed: false,
hidden: true
},
{
children: [
{
id: 'ZDTWEZFL13L8516VY480',
name: 'Interactive Work Text: Eat or Be Eaten',
activityId: 'ZDTWEZFL13L8516VY480',
nodeType: 'activity',
suppressed: false,
hidden: true,
defaultLaunchMode: 'modal'
}
],
My attempt at solving this is like this:
findNode(id, currentNode) {
console.log('id', id);
console.log('findNode', currentNode);
var i, currentChild, result, counter;
counter = 0;
console.log('first conditional statement', currentNode);
if (id && currentNode.id === id) {
return currentNode[0];
} else {
counter++;
// Use a for loop instead of forEach to avoid nested functions
// Otherwise "return" will not work properly
console.log('counter', counter);
console.log('currentNode', currentNode[counter]);
console.log('currentNode Children', currentNode.children);
for (i = counter; i < currentNode.children.length; i += 1) {
console.log(currentNode[i].children[i]);
currentChild = currentNode[i].children[i];
// Search in the current child
result = this.findNode(id, currentChild);
// Return the result if the node has been found
if (result !== false) {
return result;
}
}
// The node has not been found and we have no more options
return false;
}
}
The code above fails because I having an extremely difficult time keeping track of a counter to loop through everything.
I also added a sample picture of my data output to give you a better example of how my data is structured. Any help will be greatly appreciated.
You shouldn't need a counter to locate a single node with a matching id. Try this simpler approach:
function findNode (id, array) {
for (const node of array) {
if (node.id === id) return node;
if (node.children) {
const child = findNode(id, node.children);
if (child) return child;
}
}
}
It will return undefined if there is no match.
To avoid the need for manual iteration, you might consider using an array method like reduce instead - return the accumulator if it's truthy (that is, an object was found already), or return the object being iterated over if the ID matches, or recursively iterate over the object's children to find a match.
const data=[{id:'RAKUFNUBNY00UBZ40950',name:'Grade 1 Cover',activityId:'RAKUFNUBNY00UBZ40950',nodeType:'activity',suppressed:!1,hidden:!1},{children:[{id:'SLWDYEQHTZAFA3ALH195',name:'Build Background Video',activityId:'SLWDYEQHTZAFA3ALH195',nodeType:'activity',suppressed:!1,hidden:!1,assetReference:{referenceId:'UWFHA5A1E0EGKCM0W899',assetType:'image'}},{children:[{id:'HQUCD2SSRKMYC2PJM636',name:'Eat or Be Eaten Splash Card',activityId:'HQUCD2SSRKMYC2PJM636',nodeType:'activity',suppressed:!1,hidden:!0},{children:[{id:'ZDTWEZFL13L8516VY480',name:'Interactive Work Text: Eat or Be Eaten',activityId:'ZDTWEZFL13L8516VY480',nodeType:'activity',suppressed:!1,hidden:!0,defaultLaunchMode:'modal'}],}],}],}]
function findId(id, arr) {
return arr.reduce((a, item) => {
if (a) return a;
if (item.id === id) return item;
if (item.children) return findId(id, item.children);
}, null);
}
console.log(findId('HQUCD2SSRKMYC2PJM636', data));
If your ids are unique and finding an object by id is a common task, you might want to consider creating a lookup object to improve performance. Creating the lookup object is an O(n) task; afterwards, looking up an object by id is O(1).
const data = [ { id: 'RAKUFNUBNY00UBZ40950', name: 'Grade 1 Cover', activityId: 'RAKUFNUBNY00UBZ40950', nodeType: 'activity', suppressed: false, hidden: false }, { children: [ { id: 'SLWDYEQHTZAFA3ALH195', name: 'Build Background Video', activityId: 'SLWDYEQHTZAFA3ALH195', nodeType: 'activity', suppressed: false, hidden: false, assetReference: { referenceId: 'UWFHA5A1E0EGKCM0W899', assetType: 'image' } }, { children: [ { id: 'HQUCD2SSRKMYC2PJM636', name: 'Eat or Be Eaten Splash Card', activityId: 'HQUCD2SSRKMYC2PJM636', nodeType: 'activity', suppressed: false, hidden: true }, { children: [ { id: 'ZDTWEZFL13L8516VY480', name: 'Interactive Work Text: Eat or Be Eaten', activityId: 'ZDTWEZFL13L8516VY480', nodeType: 'activity', suppressed: false, hidden: true, defaultLaunchMode: 'modal' } ] } ] } ] } ];
const lookup = {};
const registerIds = a => {
a.forEach(o => {
if ('id' in o) {
lookup[o.id] = o;
} else if ('children' in o) {
registerIds(o.children)
}
});
}
registerIds(data);
console.log(lookup)
Sorry for my two cents, just want to add a universal method that includes nested arrays
const cars = [{
id: 1,
name: 'toyota',
subs: [{
id: 43,
name: 'supra'
}, {
id: 44,
name: 'prius'
}]
}, {
id: 2,
name: 'Jeep',
subs: [{
id: 30,
name: 'wranger'
}, {
id: 31,
name: 'sahara'
}]
}]
function searchObjectArray(arr, key, value) {
let result = [];
arr.forEach((obj) => {
if (obj[key] === value) {
result.push(obj);
} else if (obj.subs) {
result = result.concat(searchObjectArray(obj.subs, key, value));
}
});
console.log(result)
return result;
}
searchObjectArray(cars, 'id', '31')
searchObjectArray(cars, 'name', 'Jeep')
I hope this helps someone

How do I populate a hierarchy with Promises where each level needs the previous level fulfilled?

This is the first version of the code that I have attempted. I've tried a whole lot of other things like mutual exclusion, adding catch blocks everywhere, and Promise anti-patterns, but I can't seem to get over this mental or syntactical block:
populateJoins() {
let promises = [];
for (let c in this.columns) {
let transformColumn = this.columns[c];
if (transformColumn.joins) {
let joinPointer = this.databaseObject;
for (let j in transformColumn.joins) {
let join = transformColumn.joins[j];
if (joinPointer[join.as] != null) {
joinPointer = joinPointer.dataValues[join.as];
} else {
if (this.requestQuery[toCamelCase(join.as) + 'Id']) {
promises.push(
join.model.findOne({where: {id: this.requestQuery[toCamelCase(join.as) + 'Id']}})
.then((tmp) => {
joinPointer.dataValues[join.as] = tmp;
}));
} else if (joinPointer[toSnakeCase(join.as) + '_id']) {
promises.push(
join.model.findOne({where: {id: joinPointer[toSnakeCase(join.as) + '_id']}})
.then((tmp) => {
joinPointer.dataValues[join.as] = tmp;
}));
}
}
}
}
}
return Promises.all(promises);
}
And this is the structure of this.columns:
child1Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child1'),
as: 'Child1'
}],
hidden: true
},
child2Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child2'),
as: 'Child2'
}],
hidden: true
},
child1Status1Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child1'),
as: 'Child1'
},{
model: Database.getInstance().getModel('status1'),
as: 'Status1'
}],
hidden: true
},
child1Status2Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child1'),
as: 'Child1'
},{
model: Database.getInstance().getModel('status2'),
as: 'Grandchild2'
}],
hidden: true
},
serverName: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child1'),
as: 'Child2'
},{
model: Database.getInstance().getModel('grandchild'),
as: 'Grandchild'
},{
model: Database.getInstance().getModel('great_grandchild'),
as: 'GreatGrandchild'
}],
hidden: true
},
child2Status1Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child2'),
as: 'Child2'
},{
model: Database.getInstance().getModel('status1'),
as: 'Grandchild1'
}],
hidden: true
},
child2Status2Name: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child2'),
as: 'Child2'
},{
model: Database.getInstance().getModel('status2'),
as: 'Grandchild2'
}],
hidden: true
},
archetypeName: {
name: 'name',
forceSelect: true,
joins: [{
model: Database.getInstance().getModel('child2'),
as: 'Child2'
},{
model: Database.getInstance().getModel('archetype'),
as: 'Archetype'
},{
model: Database.getInstance().getModel('archetype'),
as: 'ArchetypeLink'
}],
hidden: true
},
So for things I've already learned, joinPointer[join.as] != null will never prevent duplicate Child database calls from firing because the property will not be populated until the promises finish resolving.
Similarly, none of the grandchildren will populate because they have to wait for the children to populate, and if the grandchildren fulfill first, then they will never make it into the child object. The same goes for great-grandchildren.
I read this answer, where he says, "If you already have them in an array then they are already executing." I understand that the contents of the Promise will already resolve, which is why in other code I always use numerical indices to populate objects, i.e. jsonObject['list'][i]['anotherList'][j] = ...;, but I don't see how I can do this here.
I've been working on this for a while and haven't come up with a solution, so any workable code is more than appreciated.
The code in the question is difficult to follow but it appears that what you are trying to do is reasonably simple, ie execute a set of asynchronous findOne queries in series and progressively construct an ever-deeper hierarchy comprising the returned results.
If so, then :
you can use a reduce() pattern, see "The Collection Kerfuffle" here.
the full .columns object is unnecessary - you just need .columns.greatGrandchildName.joins.
The code should look at least something like this :
populateJoin(joins) {
return joins.reduce((p, join) => {
return p.then(obj => {
let id = this.requestQuery[toCamelCase(join.as) + 'Id'] || obj[toSnakeCase(join.as) + '_id'] || obj[toSnakeCase(join.as + 's') + '_id'] || null;
if(id) {
return join.model.findOne({'where': { 'id': id }}).then(tmp => {
if (obj.dataValues[join.as]) {
return obj.dataValues[join.as];
} else {
obj.dataValues[join.as] = tmp;
return tmp; // 'tmp' will be `obj` at next iteration of the reduction.
}
return tmp; // 'tmp' will be `obj` at next iteration of the reduction.
});
} else {
return obj; // send unmodified obj to next iteration of the reduction.
}
});
}, Promise.resolve(this.databaseObject)) // starter promise for the reduction
.then(() => this.databaseObject); // useful though not essential to make the top level object available to the caller's .then() callback.
}
populateJoins() {
var promises = [];
for (let c in this.columns) {
let transformColumn = this.columns[c];
if (transformColumn.joins) {
promises.push(this.populateJoin(transformColumn.joins));
}
}
return Promise.all(promises);
}

AngularJS evaluate a string in a function to call property on json list

I wish I could give this a more descriptive title, but I don't really know the name of what I am trying to do. I have a JSON list in angular that looks like this:
$scope.users =
{
// list name and the "title" must be the same
Guest:
{
title: 'Guest',
list:
[
{ id: "0", name: "Stephen" },
{ id: "1", name: "Mitch"},
{ id: "2", name: "Nate"},
{ id: "3", name: "Rob" },
{ id: "4", name: "Capt. Jack"},
{ id: "5", name: "Herman" }
]
},
Admin:
{
title: 'Admin',
list:
[]
}
};
And I need to dynamically evaluate a string (either "Guest" or "Admin" or any other user-group that hasn't been created) in order to move a user from one user-group to another.
The function I am working with looks like:
$scope.moveUser = function(fromId, toId, index) {
scope.users.toId.list.push(scope.users.fromId.list[index]);
scope.users.fromId.list.splice(index, 1);
};
with "fromId" and "toId" being strings that evaluate to the name of a user-group ("Admin" or "Guest"). Right now, the function is trying to find a JSON field called "toId" and errors when it can't find any. How would I evaluate the string first so that if the toId == "Guest" and the fromId == "Admin", my function becomes:
scope.users.Guest.list.push(scope.users.Admin.list[index]);
scope.users.Admin.list.splice(index, 1);
change your $scope.moveUser function to
$scope.moveUser = function(fromId, toId, index) {
$scope.users[toId].list.push($scope.users[fromId].list[index]);
$scope.users[fromId].list.splice(index, 1);}
it is really work
If I understand correctly:
$scope.moveUser = function(fromId, toId, index) {
if (users.hasOwnProperty(fromId) && users.hasOwnProperty(toId)) {
scope.users.toId.list.push(scope.users.fromId.list[index]);
scope.users.fromId.list.splice(index, 1);
return true;
} else {
return false;
}
};

dijit.tree with empty folders

i have created a tree select that shows a dijit.tree in the dropdown. Now I do not want the user to select a folder even if it is empty. User should only be able to select the end nodes or leaves. dijit.tree treats all empty folders as leafs. how do I get that sorted?
You need to override the _onClick or setSelected methods. This gets complicated if you use the multi-parental model ForestStoreModel.
See fiddle.net
Try doing as such, this will only work for select multiple false:
getIconClass: function fileIconClass(item, nodeExpanded) {
var store = item._S,
get = function() {
return store.getValue(item, arguments[0]);
};
// scope: dijit.Tree
if (item.root || get("isDir")) {
if (!item || this.model.mayHaveChildren(item) || get("isDir")) {
return (nodeExpanded ? "dijitFolderOpened" : "dijitFolderClosed");
} else {
return "dijitLeaf";
}
} else {
return "dijitLeaf";
}
},
onClick: function(item, treeNode, e) {
var store = item._S,
get = function() {
return store.getValue(item, arguments[0]);
};
if (get("isDir")) this.set("selectedItems", []);
}
Adapt as you see fit, matching your json data - in particular the isDir, the above works on a sample of json like this
{
identifier: 'id',
label: 'foo',
items: [
{
id: 'item1',
foo: 'file1',
isDir: false},
{
id: 'item2',
foo: 'emptyDir',
isDir: true},
{
id: 'item3',
foo: 'dir',
isDir: true,
children: [
{
id: 'item3_1',
foo: 'fileInDir',
isDir: false}
]}
]
}

Categories