Simple JS For-loop confusion - javascript

In this example, I want to remove all the pararaphes. But as you can see, the for-loop below didn't remove them all, it still left the first paragraph for me.
http://codepen.io/vinhnghi223/pen/jnkzh
article=document.getElementsByTagName("article")[0];
for (i=0; i<article.childNodes.length; i++) {
article.removeChild(article.lastChild);
}
However, if I change the code to i<4 (which is less than article.childNodes.length), which returns 5, it works.
I got why article.childNodes.length returns 5. It just confuses me because I think if it works with 4 then it should work with 5.

It's because the .childNodes collection updates with every removal. So the .length is shrinking on every iteration, while i is growing. Since you're removing the .lastChild, you're going to end up meeting in the middle, and so the first half won't be removed.
When you hard code the original length, you eliminate the problem.
If your intent is to remove all the children, then just have the condition check to see if a .lastChild exists.
while (article.lastChild) {
article.removeChild(article.lastChild);
}
"Live" node lists like this can be tricky, so you always need to be careful when modifying their elements in a loop.

That's because you are changing the collection that you are looping through. You are looping from zero and up, and at the same time you are removing child nodes so that the upper limit of the loop is getting lower. In the middle your loop counter will meat the shrinking number of child nodes, and the loop ends.
For that loop to work, you would need to preserve the initial number of child nodes:
var cnt = article.childNodes.length;
for (i = 0; i < cnt; i++) {
article.removeChild(article.lastChild);
}
However, you can just loop while there are any child nodes left:
while (article.childNodes.length> 0) {
article.removeChild(article.lastChild);
}

Related

Reduce the complexity of matching the elements of two arrays

I wrote a code that extracts column headers (the first row in a sheet) from a google sheet and compares them with an array of objects. Each object in the objects array has 3 properties: "question", "answer", and "category". The code compares the header of each column, with the "question" property pf each object in the array.
If they're similar it should add the index of the column as a key to some dictionary and set its value to be an array that holds the answer and the category of that question. No need to much explain why I'm doing this, but briefly I built this logic to be able to grade applicants answers on some questions (hence linking the index of a question to its right answer and to its category). Here is the code:
for (i = 0; i<columnHeaders[0].length; i++){
for (j=0; j<questionsObjects.length; j++){
//Get the current question and convert it to lower case
question = questionsObjects[j].question.toString().toLowerCase();
//Get column header, remove any spaces and new lines from it, and convert it to lower case
columnHeader = columnHeaders[0][i].toString().toLowerCase();
if (isStringSimilar(columnHeader, question)){
//Link the column index to its corresponding question object
var catAndAnswer = [];
catAndAnswer.push (questionsObjects[j].category.toLowerCase());
catAndAnswer.push (questionsObjects[j].rightAnswer.toLowerCase());
columnsQuestionsDictionary[i] = catAndAnswer;
} else {
SpreadsheetApp.getActive().getSheetByName("log").appendRow(["no", columnHeader, question]);
}
}
}
The code runs well, my only problem is complexity, it's very high. In some cases this method takes almost 6 minutes to execute (for this case I had around 40 columns and 7 question objects)! To decouple the nested loops, I thought of concatenating the questions values (of all objects in the questions object array) into 1 single string where I precede each question with its index in the objects array.
For example:
var str = "";
for (j=0; j<questionsObjects.length; j++){
str = str + j + questionsObjects[j].question.toString.toLowerCase();
}
Then, I can have another separate loop through the columns headers, extract each header into a string, then use regex exec method to match that header in the long questions string (str), and if it's found I would get its index in str, then subtract 1 from it to know its index in the objects array. However, it turned out that the complexity of matching a regular expression is O(N) where N is the length of the string we search in (str in this example), given that this will be inside the columns loop, I see that we still get a high complexity that can go to O(N^2).
How can I optimize those nested loops so the code runs in the most efficient way possible?
OK, so I used the way suggested by Nina Schholz in the comments and I moved columnHeader = columnHeaders[0][i].toString().toLowerCase(); to be in the outer loop instead of being in the inner one since it's only needed in the outer one.
The time needed to run the code was reduced from ~295 seconds to ~208 seconds, which is good.
I also tried switching the loops order where I made the outer loop to be the inner one and the inner one to be the outer one and updated the usage of i and j accordingly. I did that because it's always recommended to have the outer loop with less iterations and the inner one with more iterations (according to this resource), and in my case, the loop that iterates over questions object array is always expected to have number of iterations <= the other loop.
This is because if we want to calculate the complexity of 2 nested loops, it'll be (ixj) + i, where i and j represents the number of iterations of the outer and the inner loops, respectively. Switching the loops order won't impact the multiplication part (ixj) but it'll impact the addition part. So, it's always better to have the outer number of iterations smaller than the inner ones.
After doing this the final time of the run became ~202 seconds.
Of course since the loops are switched now, I moved this line to the inner loop: columnHeader = columnHeaders[0][i].toString().toLowerCase();, but at the same time I moved this question = questionsObjects[j].question.toString().toLowerCase(); to be under the outer loop because it's only needed there.

Why do we use `length-i-1` in the inner loop of bubble sort algorithm

Using javascript, It's programmed to sort the elements in an array by asc order. I tried my best to understand why the inner loop uses length-i-1, but couldn't. Can anyone please help me understand why do we use it?
function bubbleSort(arr) {
for(let i=0; i<= arr.length; i++) {
for(let j=0; j< arr.length-i-1; j++) {
if(arr[j] > arr[j+1]) {
let lesser = arr[j+1];
arr[j+1] = arr[j];
arr[j] = lesser;
}
}
}
return arr;
}
Like Daniel said in his comment, it is because those items have already been sorted (eg. the largest element that ends up at the highest index in the first iteration)
Watch the gif below, notice how the 8 ends up on the far right, and then gets surrounded by the black box, signifying that it will not need to be moved anymore, and therefor doesn't need to be checked anymore (it is greater than length-i-1).
Not checking these already 'locked in' elements helps to increase the algorithm speed.
Gif is from: Bubble Sort on Wikipedia
Let's think about it in terms of steps:
Say you have an array of 10 elements just for the sake of an example.
1st step
i = 0, j goes from 0 to 9( = 10 - 0 - 1)
So it traverses the whole array.
Now, each time that we have that the current element is bigger than the next one we switch them (by if(arr[j] > arr[j+1])), so at the end of the first iteration, on the last position we will have the max element of the array.
2nd step
i = 1, j goes from 0 to 8( = 10 - 1 - 1)
Now, we can notice that we leave out the last (9th) position, but we know that it was already the maximum from the previous step, so it was in the correct position. At the end of this iteration we will have the 2nd maximum element on the 8th position, and the process continues..
Edit: Too slow on the Phone :P
After every outer Iteration, the i-th greatest Element is at the correct place. So after the First Iteration, the greatest Element is on the far right Side. Now that we know that, we don't have to compare this element in the next round.
After the second Iteration, the second largest element is on the far right Side -1 Position. So the two largest Elements are already sorted and we don't have to consider them in the next round.
Try your algorithm with an easy example and step through it by hand. Then you should see it clearly.

JavaScript -- trying to use a loop to delete certain items in an array, but it deletes all items

I first tried a forEach loop to delete items from an array, but later learned that you shouldn't do this. So I tried to make one with a normal for loop. This is to cycle through an array containing bullets, and delete them when they go outside the game area.
for (i = 0; i < playerBullets.length; i++) {
console.log(playerBullets[i].x);
console.log(playerBullets[i]);
if (playerBullets[i].x > 800 || playerBullets[i].x < 0 || playerBullets[i].y > 600 || playerBullets[i].y < 0 ) {
playerBullets.splice(i);
}
}
The console correctly shows [i] in full, and brings up a list of all contents of the array. However, the [i].x console log only displays one value, rather than the "x" value of each object in the array.
Then as soon as the first bullet goes out of bounds, all bullets disappear. Most frustrating, and highly ineffective for killing zombies.
I have also tried looping backwards through the loop, which seems to be the recommended way but it tells me that i is undefined.
Any ideas? I feel like I'm making a very simple error, because I'm using the same structure as the code on tutorial sites, so it "should work".
Thank you!
You should be passing two parameters to the .splice() method, the start index (which you're doing) and the number of elements to delete (which you're not doing):
playerBullets.splice(i, 1);
The reason looping backwards is a good idea is that if you remove an item from the middle of the array while looping through the array then your index i will be out of sync - the index of each item after the removed one decreases by 1 so on your next loop iteration with i++ you skip over the element after the removed one. If looping forwards you'd need to decrease i by 1 after deleting an item. If looping backwards this isn't a problem.
I'm guessing you got undefined when trying to loop backwards because you started from playerBullets.length, which is 1 more than the index of the last item. You need to start from playerBullets.length - 1 and go down to 0:
for (i = playerBullets.length - 1; i >= 0; i--) {
if (playerBullets[i].x > 800 || playerBullets[i].x < 0 || playerBullets[i].y > 600 || playerBullets[i].y < 0 ) {
playerBullets.splice(i, 1);
}
}
add another argument to splice : array.splice(i, howMany);
Here's more info on splice :
If no howMany parameter is specified , all elements after index are removed.

jquery/js too slow while operating over 2000 rows

I have 2000 rows of data as follows
<div class="rr cf">
<span>VLKN DR EXP</span>
<span>01046</span>
<span>VELANKANNI</span>
<span>20:30</span>
<span>DADAR</span>
<span>10:00</span>
</div>
On a button click I am checking for text within them and updating the display of each row to block or none. The code that does this is
$('.rr').each(function(){
this.style.display="block";
});
var nodes = $(".rr");
for(var i=0;i < nodes.length; i++) {
// if data found
nodes.get(i).style.display="block";
// else
nodes.get(i).style.display="none";
}
This seems to be possibly very slow. I get chrome alert box as to kill the page.
Any ideas? what optimization can I do here?
Local Variables and Loops
Another simple way to improve the performance of a loop is to
decrement the iterator toward 0 rather than incrementing toward the
total length. Making this simple change can result in savings of up to
50% off the original execution time, depending on the complexity of
each iteration.
Taken from: http://oreilly.com/server-administration/excerpts/even-faster-websites/writing-efficient-javascript.html
Try saving the nodes.length as a local variable so that the loop doesn't have to compute it each time.
Also, you can store nodes.get(i) into a local variable to save some time if you are accessing that data a lot.
If the order isn't important, consider decrementing your for loop towards 0.
jQuery's each() loop is a bit slower than looping through the set yourself as well. You can see here that there is a clear difference.
Very simple example
You'll see that in my example, I've condensed the loop into a while loop:
var nodes = $(".rr span");
var i = nodes.length;
while(i--){
if(i%2 === 0){
nodes.get(i).style.color = "blue";}
}​
Notice that the while loop decrements i through each iteration. This way when i = 0, the loop will exit, because while(0) evaluates to false.
"Chunking" the Array
The chunk() function is designed to process an array in small chunks
(hence the name), and accepts three arguments: a “to do” list of
items, the function to process each item, and an optional context
variable for setting the value of this within the process() function.
A timer is used to delay the processing of each item (100ms in this
case, but feel free to alter for your specific use). Each time
through, the first item in the array is removed and passed to the
process() function. If there’s still items left to process, another
timer is used to repeat the process.
Have a look at Nick Zakas's chunk method defined here, if you need to run the loop in sections to reduce the chance of crashing the browser:
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
Using createDocumentFragment()
Since the document fragment is in memory and not part of the main DOM
tree, appending children to it does not cause page reflow (computation
of element's position and geometry). Consequently, using document
fragments often results in better performance.
DocumentFragment are supported in all browsers, even Internet Explorer
6, so there is no reason to not use them.
Reflow is the process by which the geometry of the layout engine's
formatting objects are computed.
Since you are changing the display property of these elements iteratively, the page mush 'repaint' the window for each change. If you use createDocumentFragment and make all the changes there, then push those to the DOM, you drastically reduce the amount of repainting necessary.
Firstly, where are the delays occurring - in the jquery code, or the data check? If it is the jquery, you could try detaching the data container element (ie the html element that contains all the .rr divs) from the DOM, make your changes, and then re-attach it. This will stop the browser re-processing the DOM after each change.
I would try
1) set display of the common parent element of all those divs, to "none"
2) loop through divs, setting display as appropriate
3) set parent element display back to block
I believe this will help because it gives the browser the opportunity to aggregate the rendering updates, instead of forcing it to fully complete each single time you change the display property. The visibility of all the child node are irrelevant if the parent isnt displayed, so the browser no longer has a need to render a change in each child until the parent becomes visible again.
ALso, I fail to see the purpose of you first looping through them all and setting them all to block before you loop again and set them to their intended value.
Don't use jQuery here, jQuery will just slow things down here.
var elements = document.getElementsByClassName('rr'),
len = elements.length;
for(var i = 0; i < len; i++)
{
var ele = elements[i];
if(ele.innerHTML.search(/01046/) != -1)
ele.style.display = "none";
}
This should be much faster.
I'm also having performance problems while looping through roughly 1500 items.
As you might have guessed, the loop itself isn't the bottleneck. It's the operation you do within it that's the problem.
So, what I migrated the load using setTimeout. Not the prettiest of solutions but it makes the browser responsive between the updates.
var _timeout_ = 0;
for(var i=0;i < nodes.length; i++)
{
setTimeout(
(function(i)
{
return function()
{
if(stuff)
{
nodes.get(i).style.display="block";
}
else
{
nodes.get(i).style.display="none";
}
}
})(i),
_timeout_
);
_timeout_ += 4;
}
This will delay every update with 4 milliseconds, if the operation takes longer, the browser will become unresponsive. If the operation takes only 2 milisecond on your slowest browser, you can set it to 3, etc. just play around with it.

Infinite loop when using generic solution to split up carousel contents

Basically, I'm getting an infinite loop and maybe I'm working too hard but I can't see why.
Context:
I'm using a carousel (Bootstrap's). The contents of the carousel is generated and pushed into one carousel slide, then the goal is to take the contents and split it up into multiple slides if the number of items inside surpass a certain pre-defined max-length property (5). I got this working fine for a specific use case of the carousel (a table being spread across the multiple slides if there are more than 5 table rows), but it's not generic enough. What happened is that the JS would take the overflown table rows (i.e. of index 5 and up), create a new slide from a harcoded HTML string in the function (a slide div containing all the markup for the table yet empty) and push those extra rows into it.
To make it more generic, I've decided to use classes like carousel_common_list and carousel_common_item which would be applied to the tbody and trs in the case I've explained. Then, I've to handle the template in a decoupled way. What I've tried to do is, take a clone of the original sole slide, empty the carousel_common_list and push any overflown carousel_common_items into it, and so on. But I get an infinite loop.
Code
What I've called a slide so far is called an item in the code (to match Bootstrap's carousel's item class for slides).
var carousels = $('div.carousel'),
carouselCommonListClass = 'carousel_common_list',
carouselCommonItemClass = 'carousel_common_item',
items_per_slide = 5;
$.each(carousels, function (index, element) {//for each carousel
var $carousel = carousels.eq(index),
$items = $carousel.find('.item');
var getItemTemplate = function ($item) {
$copy = $item.clone();//take the html, create a new element from it (not added to DOM)
$copy.find('.' + carouselCommonListClass).empty();
$copy.removeClass('active');
return $copy;
}
var splitUpItem = function ($item, $itemTemplate) {
var $bigList = $item.find('.' + carouselCommonListClass), group;
while ((group = $bigList.find('.' + carouselCommonItemClass + ':gt(' + (items_per_slide - 1 ) + ')').remove()).length) {
var $newItem = $itemTemplate;
$newItem.find('.' + carouselCommonListClass).prepend(group);
$newItem.insertAfter($item);
splitUpItem($newItem, $itemTemplate);//infintely called
}
}
//foreach item
$.each($items, function (item_index, item_element) {//for each slide, in each carousel
var $item = $items.eq(item_index);
splitUpItem($item, getItemTemplate($item));
});
});
FYI, this works like expected when the line marked with //infintely called is commented out; i.e. splits one oversized slide into one slide of items_per_slide length and another slide (which could be over items_per_slide in length if the original sole slide was over items_per_slide * 2 in length.
Also, I took this answer and modified it for the contents of splitUpItem().
Note:
I know it's not the most usable or accessible solution to split tables, lists, etc. over multiple slides like I am, but if you've a better idea answer my open question on that.
You're not getting an infinite loop per se, in that you're not infinitely stuck in the same while loop. As you mention, when you remove the //infinitely called line you're fine. The first pass through that while loop, the length you compute will equal the number of items (with gt:(4)) in all the lists in $item. You then remove all those items, so the next pass through will have that number equal to 0. This will always be the behaviour of that loop, so it really doesn't need to be a loop, but that's not the main problem.
The problem is that it's a recursive call. And the only guard you have against making the recursive call infinite is the condition in your while loop, but that condition will always be met the first pass through. In fact, if $item has 5 lists, each with 3 items with gt:(4), then $newItem will have 5 lists, each with 5 x 3 = 15 items. So when splitUpItem gets called on $newItem, the condition in your while loop will again first be non-zero. And then it'll get called again, and that number will be 5 x 15 = 75. And so on. In other words, you're recursively calling this function, and your guard against this call being made infinitely many times is to check that some number is 0, but the number there will actually grow exponentially with each recursive call of splitUpItem.
Hope that answers your question about why it's "infinitely looping." Gotta get to work, but I'll try to suggest a better way to split up the slides tomorrow if no one else has by then.

Categories