better way to code recursive if statements - javascript

I find myself in this position occasionally, and I'm sure there is a better way to do it than I am currently.
In this example, I'm trying to sort a group of times where I have conflicting items. I need to know what times are high-priority,can't be moved, vs low priority, can be moved.
But I'm pretty sure this code is very inefficient.
var holdLowPriorities = [];
for (conflict = 0; conflict < sortedTimes.length - 1; conflict++) {
var firstConflictTimeStart = sortedTimes[conflict][0];
var firstConflictTimeEnd = sortedTimes[conflict][1];
var secondConflictTimeStart = sortedTimes[conflict + 1][0];
var secondConflictTimeEnd = sortedTimes[conflict + 1][1];
if (firstConflictTimeStart < secondConflictTimeEnd &&
firstConflictTimeEnd > secondConflictTimeStart) {
// are either of the conflicts a high priority
var firstContactPriority = sortedTimes[conflict][2];
var secondContactPriority = ortedTimes[conflict + 1][2]
if (firstConflictPriority == 2) {
//this is high priority, can't move
}
if (secondConflictPriority == 2) {
// this is also a priority, but has to move
}
// are either of the conflicts a low priority?
if (firstConflictPriority == 0) {
// this is a low priority so I can adjust the time
} else if (secondConflictPriority == 0) {
// this is a low priority so I can adjust the time
}
}
}
Unfortunately, I don't even know what to call this type of a problem, and therefore don't know what to look for, though I'm sure the answer isn't overly complicated (I hope not anyway).

A switch statement might help clean up your code a bit.

Edit: you've altered the question so most of this becomes irrelevant.
Here is a simple clarification of it.
var holdLowPriorities = [];
for(conflict=0; conflict<sortedTimes.length-1; conflict++){
var conflictTime = sortedTimes[conflict],
nextConflictTime = sortedTimes[conflict + 1];
if (conflictTime[0] >= nextConflictTime[1] || conflictTime[1] <= nextConflictTime[0]) {
continue;
}
// are either of the conflicts a high priority
if (data[conflictTime[2]].stepsArr[conflictTime[3]].priority==2) {
alert(data[conflictTime[2]].stepsArr[conflictTime[3]].
}
if (data[nextConflictTime[2]].stepsArr[nextConflictTime[3]].priority == 2) {
alert(data[nextConflictTime[2]].stepsArr[nextConflictTime[3]].
}
// are either of the conflicts a low priority?
if (data[conflictTime[2]].stepsArr[conflictTime[3]].priority==0) {
holdLowPriorities.push([conflictTime[2], conflictTime[3], conflict]);
} else if (data[nextConflictTime[2]].stepsArr[nextConflictTime[3]].priority == 0) {
holdLowPriorities.push([nextConflictTime[2], nextConflictTime[3], conflict+1])
}
//alert(data[nextConflictTime[2]].stepsArr[nextConflictTime[3]].prerequisite+' '+conflictTime[0]+' '+conflictTime[1]+' '+nextConflictTime[0]+' '+nextConflictTime[1]+' '+data[nextConflictTime[2]].stepsArr[nextConflictTime[3]].taskid+' / '+data[conflictTime[2]].stepsArr[conflictTime[3]].taskid);
}
Then this can be made more obvious by using a helper method and a couple more variables.
function conflictData(conflict) {
return data[conflict[2]].stepsArr[conflict[3];
}
var holdLowPriorities = [];
for(conflict=0; conflict<sortedTimes.length-1; conflict++){
var conflictTime = sortedTimes[conflict],
nextConflictTime = sortedTimes[conflict + 1];
if (conflictTime[0] >= nextConflictTime[1] || conflictTime[1] <= nextConflictTime[0]) {
continue;
}
var thisConflictData = conflictData(conflictTime),
nextConflictData = conflictData(nextConflictTime);
// are either of the conflicts a high priority
if (thisConflictData.priority == 2) {
alert(thisConflictData);
}
if (nextConflictData.priority == 2) {
alert(nextConflictData);
}
// are either of the conflicts a low priority?
if (thisConflictData.priority == 0) {
holdLowPriorities.push([conflictTime[2], conflictTime[3], conflict]);
} else if (nextConflictData.priority == 0) {
holdLowPriorities.push([nextConflictTime[2], nextConflictTime[3], conflict+1])
}
//alert(nextConflictData.prerequisite + ' ' + conflictTime[0] + ' ' + conflictTime[1] + ' ' + nextConflictTime[0] + ' ' + nextConflictTime[1] + ' ' + nextConflictData.taskid + ' / ' + thisConflictData.taskid);
}
When expressed like this, I think you can start to see probable bugs; high priority has conflictTime and nextConflictTime checks as if, if, whereas low priority has if, else if. Is this what is desired? You may be able to then put this/next conflict priority in a switch or reorganise the code more. But I think it's now at the stage where it can be understood more readily.

In terms of data structures, there is nothing inherently inefficiently with if..else blocks. Even nested if..else are not a problem. In terms of readability it's another matter altogether. As a rule of thumb, large and deeply nested if..else blocks are hard to follow (for a human, computers of course have no problems with them).
First of all, I'd go with Mitch's suggestion to use intermediate variables to reduce code verbosity. In terms of efficiency it is definitely less memory efficient (though for javascript, it could potentially speed up access depending on the browser). But efficiency is not the point, code clarity is.
Secondly, use array literal syntax instead of new Array() : holdLowPriorities.push([...]). Again, less noise on the screen, the easier it is to see what's going on.
Third, there are several places where all you're doing is checking the priority of something. Use either a helper function or a method to simplify the code here: checkProirity(sortedTimes,conflict,2) or sortedTimes.checkProirity(conflict,2). Again, a function/method call is inherently less efficient but the point is to improve code clarity for good readability.

Related

Nested If-else statements being skipped

What I'm building is a game where the computer generates a random number (1-100) and the user must guess the correct number. The goal is for the computer to compare the current guess to the previous guess and spit out a statement: "hot", "cold", "hotter", "colder", etc.
My Code (focus on the JS): CodePen fiddle
//global variables--computer generated guess, and guess log
var answer = Math.floor((Math.random() * 100)+1);
var guessArray = [];
var index = 0;
//user clicks submit button and guess is registered by computer
$("#submit").click( function(){
var guess = $("#guess").val();
guessArray.push(guess);
//prints out the answer and user guesses
$("#answer").text("Answer:" + " "+ answer);
$("#guessArrayPrint").text("You guessed: " + " " + guessArray + " ");
if (answer === guess) {
$("#statement").text("woo hoo right answer");
} else {
var currentDifference = Math.abs(answer-guess);
var currentDiffArray = [];
currentDiffArray.push(currentDifference);
if (index = 0) {
//if-else statement comparing current guess range to answer
if ( currentDifference >=1 && currentDifference <= 10){
$("#statement").text("Ouch! You're hot!");
} else {
$("#statement").text("Brr! You're cold!");
}
} else {
//if-else statement comparing current guess to previous guess
var previousDiff = answer- prevguess;
var prevguess = guessArray [i-1];
if( previousDiff < currentDifference){
$("#statement").text("Ahh! Getting Warmer!");
} else {
$("#statement").text("Brrr...getting colder");
}
}
index++
}
});
My nested if-else statements are not working. When a user inputs a guess, no matter how close to the answer, it always returns the statement "brr.. getting colder", which is in the "else" section.
Ideally when the user inputs their first guess if (index = 0) should run then when the second guess is input, it should move to the "else" statement with the previous guess variables. I tried moving around the variables, changed orders of if/else, and thought maybe it's the placement of index++. Nothing is working. Not sure if something is wrong with my variables , arrays, or the syntax of my if/else statements.
tl;dr: when the program is run only the "else" portion of the nested if-else statement is run. Not sure how to fix… I've gone through my code a number of times. The syntax, the arrays, and variables. Uncertain what's wrong.
You JS has if (index = 0). This should be if (index === 0).
Additionally, you need to cast the value of your input field to a number. You can do this using:
var guess = +$("#guess").val(); // + cast as a number
More syntax errors:
prevguess = guessArray[i - 1] --> prevguess = guessArray[index - 1];
Here is a partial working Fiddle. I ran through some scenarios, and the fiddle really only works if you give the application the right answer. The code has many syntax errors, bad refs and calculations. I would suggest opening the console or a debugger, identifying those issue, and fixing them.
Here is a Fully Functional Demo.

How can I adjust the color of an html element based on a certain js value smoothly and efficiently?

I'm making a little simulation where grass turns a different color based on it's energy level. Currently every grass bock goes through an if/else statement to determine what color it is every gameloop. making enough if statements to make the colors change smoothly seems very cpu intensive, and I was wondering if there was a more efficient way to do this.
simulation can be found here: http://j.mp/Wou7Sl
(the part about coloring the grass is a function called colorGrass started # line 134)
This isn't an urgent question or anything, but an answer would be appreciated.
First of all, there are some things you can do to significantly improve performance.
You have a lot of unnecessary calls to $(document).ready; these will just slow things down.
jQuery selectors are notoriously slow compared to getElementById; use that instead where performance matters, and try to look up elements as little as possible. Get a handle to the element and reuse it.
.style is much faster than jQuery's .css, use it when performance matters.
Avoid repeatedly referencing foo[bar]; instead, do baz = foo[bar] and then use baz.
Here is a demo with some of the changes described above; you'll notice that it runs more smoothly, consumes less CPU, and can run at higher frame rates. There are more optimizations to be made, but you get the idea.
Now that we've cleaned that up, we can tackle the grass. Your grass code originally looked like this:
function colorGrass(i) {
$(document).ready(function () {
if (Grasses[i].energy < 25) {
$('#' + Grasses[i].id + '').css('background-color', '#666600');
}
if (Grasses[i].energy > 25 && Grasses[i].energy < 50) {
$('#' + Grasses[i].id + '').css('background-color', '#669900');
}
if (Grasses[i].energy > 50 && Grasses[i].energy < 75) {
$('#' + Grasses[i].id + '').css('background-color', '#66cc00');
}
if (Grasses[i].energy > 75) {
$('#' + Grasses[i].id + '').css('background-color', 'green');
}
});
}
After making the changes mentioned above, it looks something like this (cleaner, don't you think?):
function colorGrass(i) {
var grass = Grasses[i];
var el = document.getElementById(grass.id);
if (grass.energy < 25) {
el.style.backgroundColor = '#666600';
}
else if (grass.energy < 50) {
el.style.backgroundColor = '#669900';
}
else if (grass.energy < 75) {
el.style.backgroundColor = '#66cc00';
}
else {
el.style.backgroundColor = 'green';
}
}
Now, to get the grass to gradiate smoothly, you should be able to take the "energy" value and translate it directly to the "green" channel, for example:
function colorGrass(i) {
var grass = Grasses[i];
var el = document.getElementById(grass.id);
el.style.backgroundColor = 'rgb(100,' + (grass.energy + 100) + ',0)';
}
How it should look exactly is up to you, so tweak these values as you see fit, and make any other changes you need to make (css, etc.) to make it look good.

Avoiding having to write the same word over and over again

I'm very new to javascript so this question might sound stupid. But what is the correct syntax of replacing certain words inside variables and functions. For example, I have this function:
function posTelegram(p){
var data = telegramData;
$("#hotspotTelegram").css("left", xposTelegram[p] +"px");
if (p < data[0] || p > data[1]) {
$("#hotspotTelegram").hide()
} else {
$("#hotspotTelegram").show()
}
};
There is the word "telegram" repeating a lot and every time I make a new hotspot I'm manually inserting the word to replace "telegram" in each line. What would be a smarter way of writing that code so that I only need to write "telegram" once?
Group similar / related data in to data structures instead of having a variable for each bit.
Cache results of calling jQuery
Use an argument
function posGeneral(p, word){
// Don't have a variable for each of these, make them properties of an object
var data = generalDataThing[word].data;
// Don't search the DOM for the same thing over and over, use a variable
var hotspot = $("#hotspot" + word);
hotspot.css("left", generalDataThing[word].xpos[p] +"px");
if (p < data[0] || p > data[1]) {
hotspot.hide()
} else {
hotspot.show()
}
};
You can't always avoid this kind of repetition (this is general to all programing languages).
Sometimes, you can make generic functions or generic classes, for example a class which would embed all your data :
Thing = function(key, xpos) {
this.$element = $('#hotspot'+key);
this.xpos = xpos;
};
Thing.prototype.pos = function (p, data) {
this.$element.css("left", this.xpos[p] +"px");
if (p < this.data[0] || p > this.data[1]) {
this.$element.hide()
} else {
this.$element.show()
}
};
And we could imagine that this could be called like this :
var telegramThing = new Thing('telegram', xposTelegram);
...
telegramThing.pos(p, data);
But it's really hard to make a more concrete proposition without more information regarding your exact problem.
I recommend you read a little about OOP and javascript, as it may help you make complex programs more clear, simple, and easier to maintain.
For example, using a Thing class here would enable
not defining more than once the "#hotspotTelegram" string in your code
reusing the logic and avoid making the same code with another thing than "telegram"
not having the Thing logic in your main application logic (usually in another Thing.js file)
But don't abstract too much, it would have the opposite effects. And if you don't use objects, try to keep meaningful variable names.
var t = "Telegram";
var $_tg = $('#hotspotTelegram');
$_tg.css("left", "xpos"+t[p] + "px"); // not sure about this line, lol
$_tg.hide();
$_tg.show();
etc.
you can create a selector as variable, something like this
function posTelegram(p){
var data = telegramData;
var $sel = $("#hotspotTelegram");
$sel.css("left", xposTelegram[p] +"px");
if (p < data[0] || p > data[1]) {
$sel.hide()
} else {
$sel.show()
}
};

Javascript code for making my browser slow down

I'm writing a library for WebWorkers, and I want to test the difference between running a script in the main page thread, versus in one or more workers. The problem is: I can't find out of hand a short function which will strain my browser enough that I can observe the difference.
A quick search didn't return much, but it might just be that I don't really know what to search for; usually I try to optimise my code, not make it slower...
I'm looking for algorithms or patterns that can be easily implemented in pure Javascript, that do not depend on the DOM or XHR, and which can have an argument passed to limit or specify how far the calculation goes (no infinite algorithms); 1s < avg time < 10s.
Extra points if it can be built without recursion and if it does not incur a significant memory hog while still being as processor intensive as possible.
Try using the obvious (and bad) recursive implementation for the Fibonacci sequence:
function fib(x) {
if (x <= 0) return 0;
if (x == 1) return 1;
return fib(x-1) + fib(x-2);
}
Calling it with values of ~30 to ~35 (depending entirely on your system) should produce good "slow down" times in the range you seek. The call stack shouldn't get very deep and the algorithm is something like O(2^n).
/**
* Block CPU for the given amount of seconds
* #param {Number} [seconds]
*/
function slowdown(seconds = 0.5) {
const start = (new Date()).getTime()
while ((new Date()).getTime() - start < seconds * 1000){}
}
slowdown(2)
console.log('done')
Calling this method will slow code down for the given amount of seconds (with ~200ms precision).
Generate an array of numbers in reverse order and sort it.
var slowDown = function(n){
var arr = [];
for(var i = n; i >= 0; i--){
arr.push(i);
}
arr.sort(function(a,b){
return a - b;
});
return arr;
}
This can be called like so:
slowDown(100000);
Or whatever number you want to use.
Check out the benchmarking code referenced by the Google V8 Javascript Engine.
For some reason Bogosort comes to mind. Basically it's a sorting algorithm that consists of:
while not list.isInOrder():
list.randomize()
It has an average complexity of O(n * n!) with little memory, so it should slow things down pretty good.
The main downside is that its running time can be anywhere from O(n) to O(inf) (though really, O(inf) is pretty unlikely).
Everyone seems determined to be complicated. Why not this?
function waste_time(amount) {
for(var i = 0; i < amount; i++);
}
If you're concerned the browser will optimize the loop out of existence entirely, you can make it marginally more complicated:
function waste_time(amount) {
var tot = 0;
for(var i = 0; i < amount; i++)
tot += i;
}
Compute lots of square roots manually?
function sqrt(number, maxDecimal) {
var cDecimal = -1;
var cNumber = 0;
var direction = -1;
while(cNumber * cNumber !== number && cDecimal < maxDecimal) {
direction = -direction;
cDecimal++;
while((cNumber * cNumber - number) / Math.abs(cNumber * cNumber - number) === direction) cNumber += direction * Math.pow(10, -cDecimal);
}
return Math.abs(cNumber);
}
function performTest() {
for(var i = 0; i < 10000; i++) {
sqrt(i, 3);
}
}
Maybe this is what you are looking for:
var threadTest = function(durationMs, outputFkt, outputInterval) {
var startDateTime = (new Date()).getTime();
counter = 0,
testDateTime = null,
since = 0,
lastSince = -1;
do {
testDateTime = (new Date()).getTime();
counter++;
since = testDateTime - startDateTime;
if(typeof outputFkt != 'undefined' && lastSince != since && testDateTime % outputInterval == 0) {
outputFkt(counter, since);
lastSince = since;
}
} while(durationMs > since);
if(typeof outputFkt != 'undefined') {
outputFkt(counter, since);
}
return counter;
}
This method will simply repeat a check in a loop
durationMS - duartion it should run in miliseconds
OPTIONAL:
outputFkt - a callback method, for logging purpose function(currentCount, milisecondsSinceStart)
outputInterval - intervall the output function will be called
I figured since you do not want to test a real function, and even NP-Hard Problems have a ratio between input length and time this could be a easy way. You can measure performance at any interval and of course receive the number of loops as a return value, so you can easily measure how much threads interfere each others performance, with the callback even on a per cycle basis.
As an example here is how i called it (jQuery and Dom usage are here, but as you can see optional)
$(document).ready(function() {
var outputFkt = function(counter, since) {
$('body').append('<p>'+counter+', since '+since+'</p>');
};
threadTest(1000, outputFkt, 20);
});
A last Warning: Of course this function can not be more exact than JS itself. Since modern Browsers can do much more than one cycle in one Milisecond, there will be a little tail that gets cut.
Update
Thinking about it... actually using the ouputFkt callback for more than just output could give great insight. You could pass a method that uses some shared properties, or you could use it to test great memory usage.

Javascript code to shorten long text needs optimization

NOTE: Originally had this listed as a memory leak. After looking into this deeper, I discovered that it's not a memory issue. It's just a very slow script. Any suggestions to speed this up would be greatly appreciated.
ANOTHER NOTE: After looking into this even further, I see that FF does not support any type of CSS that formats text in overflow. There is a hack and a workaround for that hack...but that will not be a suitable solution.
I have voted for and joined the e-mail list on this particular bug at mozilla. It's almost six years old so I resolve that users will just have to deal with it for now. At least it's not a common scenario for our product.
Original post:
The script truncates the value of an element and appends '...' while its scrollWidth is greater than it's offsetWidth. (e.g. A value of "LastName, VeryLongFirstName"will change to something like "LastName, Ver...", depending on the width of the column)
var eTable = document.getElementById(this._eDiv.id + "_tbl");
//...lots of code here...
//function called that gets all cells in a table, loops through them and clips the text
addEventListenerEx(window, "load", function() {
var aCells = eTable.getElementsByTagName("DIV");
window.alert(aCells.length);
//When aCells is length of 100, we're ok...but when it's big (like 3,000) I have problems
for (var i = 0; i < aCells.length; i++){
Grid.clipText(aCells[i]);
}
}, false);
//...lots of code here...
//This is the function doing the actual clipping
Grid.clipText = function (oDiv) {
//for tooltip
var oCurDiv;
var oTagA;
var sToolTip;
if (oDiv.firstChild) {
if (oDiv.firstChild.firstChild){
oCurDiv = oDiv.firstChild;
while (oCurDiv) {
if (is.ie) {
oTagA = oCurDiv;
} else {
// there are some different between IE & FireFox.
oTagA = oCurDiv.firstChild.parentNode;
}
if (oTagA.tagName == "A") {
sToolTip = oTagA.innerHTML;
if (sToolTip.indexOf('<b>') > 0) {
sToolTip = sToolTip.replace('<b>',"");
sToolTip = sToolTip.replace('</b>',"");
}
if (sToolTip.indexOf('<B>') > 0) {
sToolTip = sToolTip.replace('<B>',"");
sToolTip = sToolTip.replace('</B>',"");
}
oTagA.parentNode.title = convertHTMLToText(sToolTip);
}
oCurDiv = oCurDiv.nextSibling;
}
} else {
oDiv.title = convertHTMLToText(oDiv.innerHTML);
}
}
//NOTE: Additional steps to take for non-IE browsers
if (!is.ie) {
var oText = oDiv;
while (oText.nodeType != 3) {
oText = oText.firstChild;
}
var sDisplayText = oText.nodeValue;
if (sDisplayText.length < 3) return;
var lastThree;
sDisplayText = sDisplayText.slice(0, parseInt(oDiv.offsetWidth / 5));
oText.nodeValue = sDisplayText + "...";
//NOTE: Bad things happen here because of this loop
while (oDiv.scrollWidth > oDiv.offsetWidth && sDisplayText != "") {
lastThree = sDisplayText.slice(-3);
sDisplayText = sDisplayText.slice(0, sDisplayText.length - 3);
oText.nodeValue = sDisplayText + "...";
}
oText.nodeValue = sDisplayText + lastThree.slice(0, 1) + "...";
while (oDiv.scrollWidth > oDiv.offsetWidth && sDisplayText != "") {
oText.nodeValue = sDisplayText + "...";
}
}
The code works. However, the problem is that it's called over and over again after a table is loaded on the page. When the table is huge (>1,500 cells), that's when the issue starts.
So, I'm really looking for a way to make this sample (particularly the WHILE loop) more efficient.
Nothing in that is going to leak by itself. You're probably leaking oText in the closure, can you show the surrounding code?
Btw, here is a vastly more efficient way of doing this:
http://jsfiddle.net/cwolves/hZqyj/
If you really want to keep doing it the way you are, you can estimate the cutoff point by taking the length of the string and multiplying it by the proportional width it needs to be...
e.g. if the string is 100 characters and it's 2x as long as it should be, cut it to 50 chars and re-check. Or you could implement a binary 'search' algorithm to get the correct length.
The work-around, and best answer to my problem came from basic arithmetic: cross multiplication
I posted my answer in a more popular stackoverflow thread discussing the topic in better detail.

Categories

Resources