Optimize function without looping through array - javascript

I've developed a small memory game, the game contains a function to flip cards for every turn. I'm looping thru the array containing the images every time to pick one card. I'm sure there's away to optimize this function so I don't have to loop thru the array every time in order to just turn one card. But I'm a bit clueless how to proceed and would like to get some guidance of how I possibly could optimize this function.
Can I create some sort of if statement perhaps? Note. Looking for vanilla javascript suggestions only and without creating an object to replace the array.
I'm clueless of another method then to loop thru the array.
function flipCards() {
var i; // Loopvar
// For loop
for (i = 0; i < cardsElems.length; i++) {
if (this == cardsElems[i]) {
if (cardsCounter > 1) {
return;
}
cardsElems[i].className = "brickFront";
cardsElems[i].src = "pics/" + allCards[i] + ".png";
removeListener(cardsElems[i], "click", flipCards);
if (cardsCounter < 1) {
card1 = i;
} else {
card2 = i;
}
cardsCounter += 1;
}
}
if (cardsCounter < 2) {
nextBtn.disabled = true; // Disable button
}
if (cardsCounter == 2) {
turns += 1;
turnNr.innerHTML = turns;
nextBtn.disabled = false; // Enable button
}
if (brickBack.length == 0) {
endGame();
}
} // End flipCards

how about using Loadash ?
It has plenty of function to manipulate arrays and very efficient.
you can store your data inside an object.By key(card name) value(fliped or not). then access by the key to change the value to be flipped or not.
example : {Ace: true} = {'cardName': 'isFlipped'}

Related

Javascript Performance improvement

I am Using Jquery plugin to show a dropdown which looks like this
Now in the edit page, this drop-down opens with checked checkboxes, I do this with the help of Javascript which is as below
var setValues = $("#SelectedFrameworks").val().split(",");
for (var i = 0; i < setValues.length; i++) {
var selectedElement = $("#frameworksDropDown").find('*[data-id="' + setValues[i] + '"]');
selectedElement.find("i").addClass("fa-check-square-o").removeClass("fa-square-o");
SelectParent(selectedElement);
}
function SelectParent(_element) {
var count = 0;
for (var i = 0; i < $(_element).parent().children().length; i++) {
if ($(_element).parent().children().eq(i).find("i").attr("class") == "fa select-box fa-check-square-o") {
count++;
}
}
if (count == $(_element).parent().children().length) {
$(_element).closest("ul").siblings("i").click();
}
}
I store this value first in the hidden field then use it to Check the checkboxes. (as shown in the code)
Now the problem is, it takes too much time when data is a lot. this causes the page to hang.
I found that operation
selectedElement.find("i").addClass("fa-check-square-o").removeClass("fa-square-o");
takes too much time. how can I optimize this code to have a better result
EDIT
Here is the HTML for this dropdown.
Note: this HTML is autogenarated.
Thanks.
So one of the big issues with this code is the amount of times you're calling the DOM. Everytime you do $(el) you're calling document.getElementByClassName or id etc. Which is gonna be slow and is unnecessary to make that many calls.
So you can change
function SelectParent(_element) {
var count = 0;
for (var i = 0; i < $(_element).parent().children().length; i++) {
if ($(_element).parent().children().eq(i).find("i").attr("class") == "fa select-box fa-check-square-o") {
count++;
}
}
if (count == $(_element).parent().children().length) {
$(_element).closest("ul").siblings("i").click();
}
}
To this, which accesses the DOM once, stores a reference to the element. This will cut down on the amount of DOM calls you make. The biggest advantage to this is of course, speed. I always make a point of naming jquery variables beginning with $ so that it's much easier and quicker to tell what that variable is in the future, or if someone else comes to work on your code.
function SelectParent(_element) {
var count = 0;
var $element = $(_element);
var $children = $element.parent().children();
for (var i = 0, length = $children.length; i < length; i++) {
if ($children.eq(i).find("i").attr("class") == "fa select-box fa-check-square-o") {
count++;
}
}
if (count == $children.length) {
$element.closest("ul").siblings("i").click();
}
}
Now of course you can refactor the rest to speed it up ;)

Algorithm : finding nearby value the fastest way

I have an array of sorted numbers, and a starting value. For the sake of simplicity, let's say the array has values from 1 to 20, and the starting value is 10.
The value to find can change every 5 seconds, based on user input. It can either increase or decrease and it always keeps in the range of values of the table.
The only thing I cannot know is whether the value is increasing or decreasing. I have come up with the (very) simple algorithm below.
Can you think of a way to improve it ?
Ideally, I think the best approach would be to run the two for loops simultaneously and return when value is found... Using workers for example ?
In my example, the "going down" for loops is exhausted before "going up" starts running. Which, idealy, shouldn't happen, since I'm trying to spread tries -1/+1 each time.
Btw : the reason why I'm trying to do this is because I have to run quite heavy functions in the for loops. The code is running in node.js context.
Here's a JSFiddle and the code below
const attempts = document.getElementById('attempts');
let attemptsCount = Number(attempts.textContent);
const lastValue = 10;
const valueToFind = 16; //this must be found in the least possible number of attempts
const table = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
const lookup = () => {
for (var i = lastValue; i > 0; i--) {
if (table[i] == valueToFind) {
alert('Found going down');
return;
} else {
attemptsCount++;
attempts.textContent = attemptsCount;
}
}
for (var i = lastValue; i < table[table.length-1]; i++) {
if (table[i] == valueToFind) {
alert('Found going up');
return;
} else {
attemptsCount++;
attempts.textContent = attemptsCount;
}
}
}
//find new value
lookup();
Right now, each for loop you have runs async from one another...so one side will always finish before the other side starts...which is not ideal.
Remember that for loops are set up to initialize, check if bool statement = true, and set next step...so like if statements, you can implement multiple statements in each scenario.
Reducing the loop attempts can be simple as doing it at the same time:
const lookup = () => {
for (var up = lastValue, down = lastValue-1; up < table[table.length-1] || down > 0; up++, down--) {
if (up < table[table.length-1] && table[up] == valueToFind) {
alert('Found going up');
return;
} else if (down > 0 && table[down] == valueToFind) {
alert('Found going down');
return;
} else {
attemptsCount++;
attempts.textContent = attemptsCount;
}
}
Your JSFiddle updated

JavaScript Battleship Array splice

I'm making a battleship game in JavaScript and I stumbled upon a problem
var targetString = target.replace(/\s+/g, '');
for(var i = 0; i !== inputArray.length; i++) {
for(var j = 0; j !== boats[i].usedPositions.length; j++) {
if(targetString === boats[i].usedPositions[j].toString()) {
hit = true;
boats[i].hits[j] = 1;
console.log(boats[i].hits);
currentBoat = boats[i];
boats[i].usedPositions.splice(j,1);
break;
}
}
}
if(hit && stop == false) {
alert ("Hit!");
if(allEquals(currentBoat.hits, 1) && hit) {
alert("Boat with length " + currentBoat.hits.length + " has sunken!");
sunkenBoat++;
}
}
The first piece of code checks if the entered coordinate matches a coordinate of the boats (all of these are stored in usedPositions). To prevent that the player can hit that boat again, I want to take the hit coordiante out of that array using splice. But when I do this, it doesn't alert me when a boat has sunken (second piece of code). When the line with splice in is removed, it does give the alert. Can anyone help me? Full code is found here.
splice moves the subsequent array elements down to fill the space. Your logic doesn't look like it's expecting things to move like that.
Instead of splice, you probably just want to assign some other value to that array location, e.g.:
boats[i].usedPositions[j] = " "; // Where " " is assumed not to represent a boat

JavaScript: Improving the performance of a FOR Loop to stop the browser locking up?

I have an array of objects, I loop through this array one object at a time and I make a couple of checks to see if each object in that array meets certain criteria, if that object meets this criteria I then copy a property of this object in to an array (that property also contains another object).
for(var v = 0; features.length > v; v++){
var feature = features[v];
//If the feature is on the map then we want to add it
if(feature.onScreen()){
dataArray.push(feature.attributes);
}
}
Now for some reason if this array of objects is big (5000+) in size this operation becomes very expensive and the browser just locks up for a second or two (Some times more).
I cant go in to much more info of what the code does but I was wondering given this loop, what would be the best way to give the browser a break lets say every 500 iterations and so it doesn't lock up and then continue on etc.
Thanks
Not sure... but what about putting that code into a function, then breaking out and recalling the function (say every xMs)?
eg,
var LastPos = 0;
function DoLoop()
{
for(var v = LastPos; features.length > v; v++){
var feature = features[v];
//If the feature is on the map then we want to add it
if(feature.onScreen()){
dataArray.push(feature.attributes);
}
if(v > 500)
{
LastPos = v;
break;
}
}
setTimeout('DoLoop()', 10);
}
setTimeOut() can solve this.
var maxStatement = 1000, currentIndex=0, timeoutVar = ''
var sLimit = features.length
function multiStepLoop(){
for (var i=currentIndex; i<currentIndex+maxStatement; i++){
//---DO YOUR STUFFS HERE
}
var a = sLimit-i;
currentIndex += maxStatement;
if (maxStatement >= a){ maxStatement=a }
if (a<=0){
callBackFunction() //-- function to call when your peocess finish.
}else{
timeoutVar = setTimeout('multiStepLoop()',1)
}
}
multiStepLoop();
The drawback is that you need to cover all you want to do after this process complete into a function, then run it like a callBack_Function.
Note : Jus need to set the time for setTimeout to 1 millisecond & the browser won't display the waiting-cursor.
Change the structure of the loop to :
var index = 0;
function myfunc() {
while(features.length > index){
var feature = features[v];
//If the feature is on the map then we want to add it
if(feature.onScreen()){
dataArray.push(feature.attributes);
}
index++;
//You can do this
var waitingTime = 0;
If (index% 500=0) waitingTime=100; //or 10, 20...
//Or just with a short interval
var waitingTime = 10;
setTimeOut('myfunc', waitingTime);
}
}
Or with a parameter : [I prefer this one]
function myfunc(index) {
if(!index) {
index=0;
}
while(features.length > index){
var feature = features[v];
//If the feature is on the map then we want to add it
if(feature.onScreen()){
dataArray.push(feature.attributes);
}
//You can do this
var waitingTime = 0;
If (index% 500=0) waitingTime=100; //or 10, 20...
//Or just with a short interval
var waitingTime = 10;
setTimeOut('myfunc', waitingTime);
}
}
[EDIT]
Change setTimeOut calls...
And in your code, when you call the function, don't give the param, to initialize the index!
What if you did something with a little burst and a little recursion:
Fiddle Code
Main Function
What happens is a given index is passed with a burst amount of intervals (i.e. 500), upon ending the for loop at the given number of burst iterations, it recalls itself with the same burst number. The function ends when the end of the features array is completed.
var transferFeatures = function (index, burst) {
for (var z = 0; z <= burst; z++) {
if (index === features.length) {
return;
}
var feature = features[index];
if (feature.onScreen) {
dataArray.push(feature.attributes);
}
index++;
}
if (index !== features.length) {
transferFeatures(index, burst);
}
};
Testing
To simulate load, I created an array of objects using various key-value pairs (most importantly, your attributes inner object):
//To simulate a random boolean
var randomBoolean = function () {
return Math.random() <= 0.5;
};
//A random integer
var getRand = function () {
return (Math.floor(Math.random() * 10).toString());
};
// Create a bunch of dummy objects
var randomFeatures = function (arr, i) {
for (var p = 0; p < i; p++) {
arr.push({
onScreen: randomBoolean(),
attributes: {
width: getRand(),
height: getRand(),
someAtt: "I'm just an attribute",
coolKidsRideBikes: true,
foo: "bar",
bar: "baz"
}
});
}
};
Granted, it's not the same onScreen() test you will be using, but either way, it evaluates to a boolean value. I think if you apply this concept with your code, you could have amazing results.
Everything in the Fiddle I linked too at the top is called like this:
randomFeatures(features, 5000);
console.log(features.length);
transferFeatures(0,500);
console.log(dataArray.length);
Load Testing
I simulated 5000000 (5 million) random objects being pushed onto features with a burst of 1000 and the script completed in around 3.29 seconds.

JavaScript div ordering script bug (switches order of the two divs below)

Code:
http://jsfiddle.net/s4UQP/
^ Here is the best way to see the code and how it works with the divs
But here is the code anyway:
function move(from, to) {
document.getElementById('progress').innerHTML = '...';
from = parseInt(from,10);
to = parseInt(to,10);
tbc = document.getElementById(from);
before = document.getElementById(to);
containr = document.getElementById('alldivs');
neworder = 'Order: <select><option onclick="move(' + to + ',1)">1</option><option onclick="move(' + to + ',2)">2</option><option onclick="move(' + to + ',3)">3</option></select> <br>Send up | Send down<br>Bring to front (#1) | Send to back (#4)';
document.getElementById(from).getElementsByClassName('order')[0].innerHTML = neworder;
document.getElementById(from).getElementsByClassName('number')[0].innerHTML = to;
tempdiv = document.createElement('div');
tmphtml = document.getElementById(from).innerHTML;
tempdiv.className = 'holder';
tempdiv.innerHTML = tmphtml;
n = 0;
npieces = 4;
if (from < to) {
nochanges = to - from;
fromone = from + 1;
//alert(n+' '+to+' '+fromone);
for (n = fromone; n <= to; n++) {
//alert('down');
idnum = parseInt(document.getElementById(n).id,10);
//alert(idnum);
document.getElementById(n).getElementsByClassName('number')[0].innerHTML = (idnum - 1);
alert(document.getElementById(n).id);
document.getElementById(n).id = (idnum - 1);
//alert('down '+idnum+' to '+(idnum-1));
}
}
if (from > to) {
nochanges = from - to;
totone = to + 1;
for (n = to; n < from; n++) {
//alert('n is '+n+' going to '+to+' ends at '+totone);
//alert('up');
idnum = parseInt(document.getElementById(n).id,10);
//alert(idnum);
document.getElementById(n).getElementsByClassName('number')[0].innerHTML = (idnum + 1);
alert(document.getElementById(n).id);
document.getElementById(n).id = (idnum + 1);
//alert('up '+idnum+' to '+(idnum+1));
}
}
//tempdiv.id = 'span'+to;
if (from > to) {
containr.insertBefore(tempdiv, before);
}
if (from < to) {
before = to + 1;
containr.insertBefore(tempdiv, document.getElementById(before));
}
tbc.parentNode.removeChild(tbc);
tempdiv.id = to;
document.getElementById('progress').innerHTML = 'done';
}
The script works as you move a block (or div) up or down, but when you try to move a different block (e.g. the one at the top), it just switches around the first two blocks beneath it.
Could anyone give me any advice?
I don't know whether it's because of the order that the script was done in, or if it's something else. It's been confusing me for some time, and I'd really appreciate it if someone could look through it and give me some advice.
(I don't want to code it in jQuery, this is really just me trying to learn more JavaScript by coding something. If it's not the most efficient, secure, whatever, it's still just something with which I'm trying to teach myself JavaScript.)
Thank you for reading. (Please don't edit the JS Fiddle itself, but rather post any edits/improvements here. Thank you.)
[Edit: I'm not really writing a cliche sci-fi, they're just example divs because I couldn't think of anything better]
In the statement neworder =... you change the values of the onclick functions, but you only do this for the block that is about to be moved. The problem is that the other blocks also change positions. For instance, if you click on 'Send up' for block 2, then block 2 moves up to position 1 and block 1 moves down to position 2. But only the event handlers on block 2 are updated accordingly. So the next time you click on (what was originally) block 1, it will not behave correctly.
One solution would be to update the event handlers on all of the blocks that are affected every time one of them is moved. For instance, make a function called updateEventHandlers(blockNumber) and call it for all of the affected blocks.
However relying on IDs to indicate the position of a block and then fiddling with the IDs after they are moved can lead to all sorts of confusion. It is better either to keep an array or dictionary recording the positions of the blocks, or loop through them to determine their positions in the DOM each time you want to move them.
For instance the following code provides moveup, movedown and moveto functions using the latter method (it finds where the element is in the DOM and swaps it with the holder before or after). (JSFIDDLE)
function E(id) { return document.getElementById(id);}
var holders = document.getElementsByClassName('holder');
function moveup(id) {
for (var i = 0; i < holders.length - 1; i++) {
// Find the holder before the one we're interested in
if (holders[i + 1] == E(id)) {
// Swap their positions
E('alldivs').insertBefore(E(id), holders[i]);
break;
}
}
resetNumbers();
}
function movedown(id) {
for (var i = 1; i < holders.length; i++) {
// Find the holder after the one we're interested in
if (holders[i - 1] == E(id)) {
// Swap their positions
E('alldivs').insertBefore(holders[i], E(id));
break;
}
}
resetNumbers();
}
function moveto(id, position) {
if (position == holders.length) { // move to end
E('alldivs').appendChild(E(id));
}
else { // move before another holder
E('alldivs').insertBefore(E(id), holders[position - 1]);
}
resetNumbers();
}
function resetNumbers() {
// Reset all the numbers to reflect their current position
var numbers = document.getElementsByClassName('number');
for (var i = 0; i < numbers.length; i++) {
numbers[i].innerHTML = i + 1;
}
}​
A few other points:
clicking on the selects in your original code won't do anything initially, because no event handler is assigned to it until after one of the elements has been moved
there is a missing </div> from the end of the html
it is good practice to declare variables using var somewhere in your code
appendChild and insertBefore remove a node from its current position in the DOM before appending/inserting it in its new position, so there is no need to remove the element explicitly.
having moveup and movedown functions is better than only having moveto, which requires you to insert the current, preceding and following positions into the html and refresh them every time a block is moved.

Categories

Resources