How to avoid messy nested if-else or switch - javascript

Function AM()
{
var source = new Array("External","Chassis","Internal");
var shape = new Array("Sine","Square", "Ramp","Nramp","Triangle","ARB");
var depth = new Array ("100","0","50");
var frequency = new Array("100","0.002","20000","1000");
var currentSource;
var currentShape;
var currentDepth;
var currentFrequency;
for (var item in source)
{
currentSource = source[item];
FG_AMSource().Keys(source[item] );
for (var item in shape)
{
currentShape = shape[item];
FG_AMShape().Keys(shape[item] );
for (var item in depth)
{
currentDepth = depth[item];
FG_AMDepth().Keys(depth[item]);
for (var item in frequency)
{
currentFrequency = item;
FG_AM_Frequency().Keys(frequency[item]);
CheckImage2(currentSource, currentShape,currentDepth, currentFrequency);
}// shape
}// depth
}// shape
}//source
}
FG_AMSource() is a function that allows me to set the setting. With what I have here, I am able to loop through all combinations of Source, Shape, Depth and Frequency.
My problem here lies in the CheckImage2 function. With each combination I get, I must call this CheckImage2 function, pass in the current Source, Shape, Depth and Frequency, then do checking accordingly. Therefore, inside my CheckImage2 function, it will look something like
Function CheckImage2(currentSource, currentShape, currentDepth, currentFrequency)
{
switch (currentSource)
case "External":
switch (currentShape)
case "Sine":
switch (currentDepth)
case "100":
switch (currentFrequency)
case "100": //External+Sine+100+100
case "0.002": //External+Sine+100+0.002
//etc etc etc you get the idea
//and I need to include all possible combinations
}
What should I do instead?

There are couple of ways to get around with it:
1) If the action can be formulated, then you should create the formula rather than creating nested for loop or switch. Something like
Function CheckImage2(currentSource, currentShape, currentDepth, currentFrequency)
{
//example, suppose it can be formulated as a string
string formulatedString = currentSource + currentShape + currentDepth + currentFrequency;
//do something else, because things can be formulated instead of listed
}
This is because what you actually need is a handler/function which takes any of the combination.
2) If the number of combination is not that many to populate, try using HashMap or Map or whatever equivalent and pre-populate the possible combinations such that when you use you simply need to call hashMap[key] and at most 1 for loop then act accordingly instead of having nested loops
3) Whenever possible, you can also break them into pieces of smaller independent functions which depend only on one (or at least fewer) element at a time (rather than all of them).
4) Consider of creating a proper class and using Tree structure to work around with it

Related

Optimized the color render function

I have a big data to handle.
They need to be classified into 4 colors, and render to the SVG.
My function is:(parameter A B C are used to do something....)
function mapRender(dataArray,A,B,color1,color2,color3,color4,C){
//......do somthing.......
var colorPlusAry = $.grep(dataArray, function(item,key){
var vote_percentage = parseInt(item.vote_percentage);
var result = vote_percentage>=0 && vote_percentage <50;
return result;
});
//......do somthing.......
}
I use grep to generate the new array which the item has same color, and render to SVG.
function colorDistrict(colorArray,color){
var village = '';
var fillColor = 'fill:'+color;
for(var item in colorArray) {
village = colorArray[item].village;
$('svg').find('path').each(function(){
var id = $(this).attr('id');
if(village) {
if(id.substring(6) === village){
$(this).attr('style',fillColor);
}
}
});
}
}
colorDistrict(colorPlusAry,color1); //Render 4 array
It works successfully, but the data is too large and make render slowly, when I trigger the function, it takes several seconds to react...
How can I optimize this function to render the color?
Optimization is a difficult business without the real data, and without knowing the precise DOM structure. I can only give some hints from what I see:
The costly process is interacting with the DOM. Looking at the colorDistrict() function, the two loops seem to be independent. It would then make sense to run the .each() loop only once, and the loop over the colorArray as the nested one. The latter only contains precomputed values and should run much faster.
Looking at what these loops really do, you can write them much more semantic. You compare the collection of all paths and the colorArray for intersection, and then assign a style to a filtered list of paths.
function colorDistrict (colorArray, color){
var fillColor = 'fill:' + color;
// filter the paths to find those mentioned in colorArray
var relevantPaths = $('svg').find('path').filter(function () {
var village = $(this).attr('id').substring(6);
// boolean to indicate the village is part of the colorArray
var included = colorArray.some(function (item) {
return item.village === village;
});
return included;
});
relevantPaths.attr('style', fillColor);
}
If I understand correctly what you are doing, the colorDistrict() function is executed multiple times, once for every color you assign. Do the <path> elements change between rendering the different colors? If not, you should execute $('svg').find('path') only once and cache the found paths for reuse inside that function.
var paths = $('svg').find('path');
function colorDistrict (colorArray, color){
var fillColor = 'fill:' + color;
var relevantPaths = paths.filter(function () {
...
});
}

Is there a non loop-every-single-list-item approach to find unique list items?

I know I could use the loop-every-single-list-item approach to filter out unique elements in a given list, but I feel like there's probably a neat, quick way to do it.
How can I find unique list items in JavaScript, without looping through and filtering them manually?
Lately I was working on event handling patch and needed fast method for filtering out unique function handlers in a callback lists which got to be run quite frequently.
Here's what I'm trying to do:
Array.prototype.unique = (function () {
// main Array#unique method
var uni = function uni () {
return this.filter(uni.x);
};
// attach a helper for resolving unique elements
// if element is at current position, not before,
// it's unique one, pass `true` flag to .filter()
uni.x = function (node, pos, ls) {
return pos === ls.indexOf(node);
};
// save
return uniq;
})();
Implementation:
// sample list:
// generate ~1K long list of integers:
// get the keys of string object of length 32,
// map every item to key-list itself,
// flatten, shuffle..
var ls =
Array.prototype.concat.apply([],
Object.keys(new String('1'.repeat(32)))).
map(function (node, pos, list) { return list; }).
sort(function () { return Math.random() < Math.random(); });
// run each function 1K times fetching unique values
for (
var
it = -1,
l = 1000,
// record iteration start
tm = Date.now();
++it < l;
ls.unique()
);
No. If you have a list, you will need to look at least once at every single item to determine whether it is unique.
If you need something faster, don't use a list.
Btw, even on a list you can implement a unique-algorithm in less than the O(n²) that you currently have. See Easiest way to find duplicate values in a JavaScript array for some clever approaches.
I was working on event handling patch and needed fast method for filtering out unique function handlers in a callback list which got to be run quite frequently.
Then you don't want to put them in that list in the first place. Don't check the list for duplicates when you run it (which as you say is frequent), but when you insert a new handler.
If you think that using .indexOf to find a handler in the list is still too slow, you can mark every function object that it is already contained in the list. Choose a unique (per list) property name, and put a value on that property of each function that is in the list. You can then check in constant runtime for duplicates.
If you have a unique key, using a dictionary is a good option. However, if you have some logic that needs to be executed to perform your filtering, I'd go with UnderscoreJS. Check out the _.filter method. It's a great library with lots to offer in this area.
http://underscorejs.org/#filter
I don't think there is a way to get unique list of items without iterating through each item. If you're looking for a built-in library function, I don't think there is one in Angular.
It would be simple enough to create one:
function unique(array, fn) {
var hash = [];
var list = [];
for(var i = 0; i < array.length; ++i) {
var key = fn(array[i]);
if (key && !hash[key]) {
list.push(array[i]);
hash[key] = key;
}
}
return list;
}
Usage:
var myList = [ { id:1, name="oranges"},
{ id:2, name="apples" },
{ id:1, name="oranges"},
{ id:3, name="pears" },
{ id:3, name="pears" } ];
var uniqueList = unique(myList, function(item) { return item.id; });

Store a table row index as an array index

There a simple function:
selected_row = []; // global scope
function toggleRowNumber(rowIndex) {
if(selected_row[rowIndex]) selected_row.splice(rowIndex, 1);
else selected_row[rowIndex] = 1;
}
usage
toggleRowNumber(50000); // click the row - write the index
toggleRowNumber(50000); // click the row again - remove the inxed
alert(selected_row.length);
50001
OK
Delightful feature!
So is there a way to direct write|read an index without any searchin/looping? And without this huge feat as decribed above.
Thanks.
If I understoold correctly, you want to store and index where you can check/set whether an item is selected or not. If that is the case, you are looking for a "key - value" data structure. Then, why not use a map?
var selected_row = {};
function toggleRowNumber(rowIndex) {
if(selected_row[rowIndex]) selected_row[rowIndex] = 0; //or = undefined;
else selected_row[rowIndex] = 1;
}
That is better because hash map will save you time and space.
Space becuase you are not storing hundreds of 'undefined' values in a vector.
Time because, hash function used to access elements is pretended to hit the right position in many cases.

Can I loop through 2 objects at the same time in JavaScript?

related (sort of) to this question. I have written a script that will loop through an object to search for a certain string in the referring URL. The object is as follows:
var searchProviders = {
"google": "google.com",
"bing": "bing.com",
"msn": "search.msn",
"yahoo": "yahoo.co",
"mywebsearch": "mywebsearch.com",
"aol": "search.aol.co",
"baidu": "baidu.co",
"yandex": "yandex.com"
};
The for..in loop I have used to loop through this is:
for (var mc_u20 in mc_searchProviders && mc_socialNetworks) {
if(!mc_searchProviders.hasOwnProperty(mc_u20)) {continue;}
var mc_URL = mc_searchProviders[mc_u20];
if (mc_refURL.search(mc_URL) != -1) {
mc_trackerReport(mc_u20);
return false;
}
Now I have another object let's call it socialNetworks which has the following construct:
var socialNetworks = {"facebook" : "facebook.co" }
My question is, can I loop through both of these objects using just one function? the reason I ask is the variable mc_u20 you can see is passed back to the mc_trackerReport function and what I need is for the mc_u20 to either pass back a value from the searchProviders object or from the socialNetworks object. Is there a way that I can do this?
EDIT: Apologies as this wasn't explained properly. What I am trying to do is, search the referring URL for a string contained within either of the 2 objects. So for example I'm doing something like:
var mc_refURL = document.referrer +'';
And then searching mc_refURL for one of the keys in the object, e.g. "google.com", "bing.com" etc. 9this currently works (for just one object). The resulting key is then passed to another function. What I need to do is search through the second object too and return that value. Am I just overcomplicating things?
If I understand your question correctly, you have a variable mc_refURL which contains some URL. You want to search through both searchProviders and socialNetworks to see if that URL exists as a value in either object, and if it does you want to call the mc_trackerReport() function with the property name that goes with that URL.
E.g., for mc_refURL === "yahoo.co" you want to call mc_trackerReport("yahoo"), and for mc_ref_URL === "facebook.co" you want to call mc_trackerReport("facebook").
You don't say what to do if the same URL appears in both objects, so I'll assume you want to use whichever is found first.
I wouldn't create a single merged object with all the properties, because that would lose information if the same property name appeared in both original objects with a different URL in each object such as in an example like a searchProvider item "google" : "google.co" and a socialNetworks item "google" : "plus.google.com".
Instead I'd suggest making an array that contains both objects. Loop through that array and at each iteration run your original loop. Something like this:
var urlLists = [
mc_searchProviders,
mc_socialNetworks
],
i,
mc_u20;
for (i = 0; i < urlLists.length; i++) {
for (mc_u20 in urlLists[i]) {
if(!urlLists[i].hasOwnProperty(mc_u20))
continue;
if (mc_refURL.search(urlLists[i][mc_u20]) != -1) {
mc_trackerReport(mc_u20);
return false;
}
}
}
The array of objects approach is efficient, with no copying properties around or anything, and also if you later add another list of URLs, say programmingForums or something you simply add that to the end of the array.
You could combine the two objects into one before your loop. There's several approaches here:
How can I merge properties of two JavaScript objects dynamically?
var everything = searchProviders;
for (var attrname in socialNetworks) { everything[attrname] = socialNetworks[attrname]; }
for(var mc_u20 in everything) {
// ...
}
for (var i = 0; i < mc_searchProviders.length; i++) {
var searchProvider = mc_searchProviders[i];
var socialNetwork = mc_socialNetworks[i];
if (socialNetwork != undefined) {
// Code.
}
}
Or am i horribly misunderstanding something?

alternatives for excessive for() looping in javascript

Situation
I'm currently writing a javascript widget that displays a random quote into a html element. the quotes are stored in a javascript array as well as how many times they've been displayed into the html element. A quote to be displayed cannot be the same quote as was previously displayed. Furthermore the chance for a quote to be selected is based on it's previous occurences in the html element. ( less occurrences should result in a higher chance compared to the other quotes to be selected for display.
Current solution
I've currently made it work ( with my severely lacking javascript knowledge ) by using a lot of looping through various arrays. while this currently works ( !! ) I find this solution rather expensive for what I want to achieve.
What I'm looking for
Alternative methods of removing an array element from an array, currently looping through the entire array to find the element I want removed and copy all other elements into a new array
Alternative method of calculating and selecting a element from an array based on it's occurence
Anything else you notice I should / could do different while still enforcing the stated business rules under Situation
The Code
var quoteElement = $("div#Quotes > q"),
quotes = [[" AAAAAAAAAAAA ", 1],
[" BBBBBBBBBBBB ", 1],
[" CCCCCCCCCCCC ", 1],
[" DDDDDDDDDDDD ", 1]],
fadeTimer = 600,
displayNewQuote = function () {
var currentQuote = quoteElement.text();
var eligibleQuotes = new Array();
var exclusionFound = false;
for (var i = 0; i < quotes.length; i++) {
var iteratedQuote = quotes[i];
if (exclusionFound === false) {
if (currentQuote == iteratedQuote[0].toString())
exclusionFound = true;
else
eligibleQuotes.push(iteratedQuote);
} else
eligibleQuotes.push(iteratedQuote);
}
eligibleQuotes.sort( function (current, next) {
return current[1] - next[1];
} );
var calculatePoint = eligibleQuotes[0][1];
var occurenceRelation = new Array();
var relationSum = 0;
for (var i = 0; i < eligibleQuotes.length; i++) {
if (i == 0)
occurenceRelation[i] = 1 / ((calculatePoint / calculatePoint) + (calculatePoint / eligibleQuotes[i+1][1]));
else
occurenceRelation[i] = occurenceRelation[0] * (calculatePoint / eligibleQuotes[i][1]);
relationSum = relationSum + (occurenceRelation[i] * 100);
}
var generatedNumber = Math.floor(relationSum * Math.random());
var newQuote;
for (var i = 0; i < occurenceRelation.length; i++) {
if (occurenceRelation[i] <= generatedNumber) {
newQuote = eligibleQuotes[i][0].toString();
i = occurenceRelation.length;
}
}
for (var i = 0; i < quotes.length; i++) {
var iteratedQuote = quotes[i][0].toString();
if (iteratedQuote == newQuote) {
quotes[i][1]++;
i = quotes.length;
}
}
quoteElement.stop(true, true)
.fadeOut(fadeTimer);
setTimeout( function () {
quoteElement.html(newQuote)
.fadeIn(fadeTimer);
}, fadeTimer);
}
if (quotes.length > 1)
setInterval(displayNewQuote, 10000);
Alternatives considered
Always chose the array element with the lowest occurence.
Decided against this as this would / could possibly reveal a too obvious pattern in the animation
combine several for loops to reduce the workload
Decided against this as this would make the code to esoteric, I'd probably wouldn't understand the code anymore next week
jsFiddle reference
http://jsfiddle.net/P5rk3/
Update
Rewrote my function with the techniques mentioned, while I fear that these techniques still loop through the entire array to find it's requirements, at least my code looks cleaner : )
References used after reading the answers here:
http://www.tutorialspoint.com/javascript/array_map.htm
http://www.tutorialspoint.com/javascript/array_filter.htm
http://api.jquery.com/jQuery.each/
I suggest array functions that are mostly supported (and easily added if not):
[].splice(index, howManyToDelete); // you can alternatively add extra parameters to slot into the place of deletion
[].indexOf(elementToSearchFor);
[].filter(function(){});
Other useful functions include forEach and map.
I agree that combining all the work into one giant loop is ugly (and not always possible), and you gain little by doing it, so readability is definitely the winner. Although you shouldn't need too many loops with these array functions.
The answer that you want:
Create an integer array that stores the number of uses of every quote. Also, a global variable Tot with the total number of quotes already used (i.e., the sum of that integer array). Find also Mean, as Tot / number of quotes.
Chose a random number between 0 and Tot - 1.
For each quote, add Mean * 2 - the number of uses(*1). When you get that that value has exceeded the random number generated, select that quote.
In case that quote is the one currently displayed, either select the next or the previous quote or just repeat the process.
The real answer:
Use a random quote, at the very maximum repeat if the quote is duplicated. The data usages are going to be lost when the user reloads/leaves the page. And, no matter how cleverly have you chosen them, most users do not care.
(*1) Check for limits, i.e. that the first or last quota will be eligible with this formula.
Alternative methods of removing an array element from an array
With ES5's Array.filter() method:
Array.prototype.without = function(v) {
return this.filter(function(x) {
return v !== x;
});
};
given an array a, a.without(v) will return a copy of a without the element v in it.
less occurrences should result in a higher chance compared to the other quotes to be selected for display
You shouldn't mess with chance - as my mathematician other-half says, "chance doesn't have a memory".
What you're suggesting is akin to the idea that numbers in the lottery that haven't come up yet must be "overdue" and therefore more likely to appear. It simply isn't true.
You can write functions that explicitly define what you're trying to do with the loop.
Your first loop is a filter.
Your second loop is a map + some side effect.
I don't know about the other loops, they're weird :P
A filter is something like:
function filter(array, condition) {
var i = 0, new_array = [];
for (; i < array.length; i += 1) {
if (condition(array[i], i)) {
new_array.push(array[i]);
}
}
return new_array;
}
var numbers = [1,2,3,4,5,6,7,8,9];
var even_numbers = filter(numbers, function (number, index) {
return number % 2 === 0;
});
alert(even_numbers); // [2,4,6,8]
You can't avoid the loop, but you can add more semantics to the code by making a function that explains what you're doing.
If, for some reason, you are not comfortable with splice or filter methods, there is a nice (outdated, but still working) method by John Resig: http://ejohn.org/blog/javascript-array-remove/

Categories