I have a recursive function which bubblesorts through an array in Javascript. The function calls on itself, which results in it exceeding the stack size of the browser and returning the error:
RangeError: Maximum call stack size exceeded
I understand the problem, and I've tried to wrapping the line which calls itself with setTimeout. This works, but, even if I set the time to 1ms, the sorting is significantly slower than if the setTimeout didn't exist.
Here's the function:
var pieces = [........]; // jumbled array
bubbleSort(0);
function bubbleSort(a) {
if (a < bars-1) {
onBar = a;
} else {
onBar = 0;
}
if (pieces[onBar] < pieces[onBar + 1]) {
// Correct order
bubbleSort(onBar + 1);
} else {
// Incorrect order
var p1 = pieces[onBar];
var p2 = pieces[onBar + 1];
pieces[onBar] = p2;
pieces[onBar + 1] = p1;
bubbleSort(onBar + 1);
}
}
For some strange reason, if I wrap one of the call lines in a setTimeout and leave the other untouched the function runs without any errors, but as soon as I leave both unwrapped it returns an error.
Thanks for your time. Any help appreciated.
You need a branch where you return without ever calling bubbleSort.
Related
in Js, I want to try to simulate a die roll by showing images of die 1 to 6, but when I try to display these in a for loop, it only displays image dice6. I tried putting in a nested for loop to slow down the outer loop but that didnt work. Does the page need to refresh after changing "src" attribute?
const dieImage = function (num) {
return "images/dice" + String(num).trim() + ".png";
};
function dieRoll(num) {
for (let i = 1; i < 7; i++) {
for (let z = 0; z < 44444; z++) {} // attempt to slow
if (num === 1) {
img1.setAttribute("src", dieImage(i));
} else {
img2.setAttribute("src", dieImage(i));
}
}
}
As mentioned in the comments you can use setTimeout. You can introduce a delay and give the browser a chance to redraw by using setTimeout, promise, and await, for example like this:
const DELAY = 300; // 300ms
async function dieRoll(num) {
for (let i = 1; i < 7; i++) {
if (num === 1) {
img1.setAttribute("src", dieImage(i));
} else {
img2.setAttribute("src", dieImage(i));
}
await new Promise((resolve) => setTimeout(() => resolve(), DELAY));
}
}
The loop execution will stop until the promise is resolved, but it will let the browser to continue to respond and redraw. When the promise is resolved after the timeout callback is run after the given DELAY milliseconds, the next iteration of the loop will take place.
What you are missing (roughly) is that the browser paints the screen when the JavaScript code has finished running. Even though you are setting the src attribute to a different image in a loop, the JavaScript code finishes only when the loop ends. The browser paints only once, i.e. the last image you set in the loop. This explanation may be oversimplified, but it gives you an idea.
The solution is to return from the JavaScript code after setting the src and repeating after a suitable delay, giving the user the opportunity to sense the change. setTimeout is probably good enough for your case; in other use cases where you want really smooth animation, there would be other solutions (e.g. requestAnimationFrame()). An untested implementation to demonstrate the intent:
function dieRoll(selectedNum) {
var counter = 8; // how many times to change the die
function repeat() {
if (counter === 1) {
// on the last iteration, set the image representing the selected number
img1.setAttribute("src", dieImage(selectedNum));
} else {
// else decrement the counter, set a random image and repeat after a timeout
counter--;
img1.setAttribute("src", dieImage(Math.floor(6 * Math.random()) + 1));
setTimeout(repeat, 300);
}
}
repeat();
}
I am trying to build a simple slider by changing the background image, but i am not sure why I am getting an error that says too much recursion.
document.addEventListener("DOMContentLoaded", function(event) {
let headerImages = [];
let header = document.querySelector('.header');
let i = 0;
let time = 3000;
headerImages[0] = 'img/header1.jpg';
headerImages[1] = 'img/header2.jpg';
function changeHeaderImg() {
header.style.backgroundImage = "url(" + headerImages[i] + ")";
if(i < headerImages.length - 1){
i++;
} else {
i = 0;
}
setTimeout(changeHeaderImg(), time);
}
changeHeaderImg();
});
You are calling changeHeaderImg and passing it's result to setTimeout instead of passing changeHeaderImg itself.
So you are getting endless recursion which results in so-called "stack overflow" classic error.
Try setTimeout(changeHeaderImg, time);
A function that calls itself is called a recursive function. Once a condition is met, the function stops calling itself. This is called a base case.
In some ways, recursion is analogous to a loop. Both execute the same code multiple times, and both require a condition (to avoid an infinite loop, or rather, infinite recursion in this case). When there are too many function calls, or a function is missing a base case, JavaScript will throw this error.
function loop(x) {
if (x >= 10) // "x >= 10" is the exit condition
return;
// do stuff
loop(x + 1); // the recursive call
}
loop(0);
Setting this condition to an extremely high value, won't work:
function loop(x) {
if (x >= 1000000000000)
return;
// do stuff
loop(x + 1);
}
loop(0);
// InternalError: too much recursion
This recursive function is missing a base case. As there is no exit condition, the function will call itself infinitely.
function loop(x) {
// The base case is missinng
loop(x + 1); // Recursive call
}
loop(0);
// InternalError: too much recursion
Ive posted other questions but feel I should simplify things
I have one function that sets a context and calls a second function to draw the lines on the context.
I have this:
var arr = [];
which is populated like this:
arr = [context,pre];
while pre looks like pre = [[{x:n,y:m}],[{x:j,y:k}]];
So, basically, I have an array, pre, containing arrays of coordinates. That array is pushed with a context into arr
arr is returned and pushed into a final array, lets say final_arr, which now should look like this: final_arr = [[context1,pre1],[context2,pre2],...]
My goal is to loop through final_arr and draw lines on different contexts, determined by the context in the array. For example, the first iteration will access final_arr[0] and contain context1,pre1. These two values are sent to a function, wrap(context, pre) that returns a promise. Inside this wrap function, another function is called, animate(pre[i]). this function takes each element in pre, which corresponds to an array of coordinates, and actually draws the line using animation frames. animate() also returns a promise.
Currently, only one of the paths is being drawn, which seems to be because only one value of final_arr is being used, even though I am iterating through it
My attempts to iterate:
final_arr.reduce((a,c) => a.then(() => wrap(c[0],c[1])), Promise.resolve());
and
var temp = Promise.resolve();
var i = 0;
for (i = 0; i < arr.length; i++){
//window.alert(arr[i].length)
var ct = arr[i][0];
var line = arr[i][1];
temp.then(() => wrap(ct,line));
}
and here are the functions being called:
/*
* Animation function draws a line between every point
*/
var animate = function(p){
return new Promise(function(resolve) {
t = 1;
var runAnimation = function(){
if(t<p.length){
context.beginPath();
context.moveTo(p[t-1].x,p[t-1].y);
context.lineTo(p[t].x,p[t].y);
context.stroke();
t++;
requestAnimationFrame(function(){runAnimation()});
} else {
resolve()
}
};
runAnimation();
});
}
function wrap(ctx, lines){
return new Promise(function(resolve) {
var counter = 0;
t = 1;
var getAnimation = function(){
if(counter < lines.length){
context = ctx;
lines.reduce((a, c) => a.then(() => animate(c)), Promise.resolve());
counter++;
} else {
resolve()
}
};
getAnimation();
});
}
The context variable set in wrap is a global variable for the js file
I hope the question asked this way provides clarity as to what I am having a problem with
Thank you for any help
Edit:
Attempted fiddle
Edit2:
Oddly enough this works
if(final_arr.length == 1){
wrap(final_arr[0][0], final_arr[0][1]);
} else if (final_arr.length == 2){
wrap(final_arr[0][0], final_arr[0][1]).then(wrap(final_arr[1][0], final_arr[1][1]));
} else if (final_arr.length == 3){
wrap(final_arr[0][0], final_arr[0][1]).then(wrap(final_arr[1][0], final_arr[1][1])).then(wrap(final_arr[2][0], final_arr[2][1]));
}
But when using this, the lines are drawn at the same time (which is okay, but not preferred)
edit: just spotted the missing resolve inside the if statement of wrap => the returned Promise will never be resolved...
I recommend to start with a much simpler version of wrap before making any micro-optimizations:
function wrap(ctx, lines){
return new Promise(function(resolve) {
lines.forEach(p => animate(p, ctx));
resolve();
});
}
callbacks to requestAnimationFrame are called after finishing all microtasks (i.e. after all Promises) - see When will requestAnimationFrame be executed?
so the value of the global variable context will be the same for all of the callbacks, i.e. the same line drawn multiple times or a race condition or something depending on internals of the context
I would get rid of globals, using only function params and locals:
var animate = function(p, ctx) {
var t ...
... ctx.beginPath()
Is there anybody would explain why the result is different below?
// test one
function computeMaxCallStackSize() {
try {
return computeMaxCallStackSize() + 1;
} catch (e) {
return 1;
}
}
console.log(computeMaxCallStackSize());
result is 17958
// test two
function computeMaxCallStackSize() {
try {
return 1 + computeMaxCallStackSize();
} catch (e) {
return 1;
}
}
console.log(computeMaxCallStackSize());
result is 15714
When the position of the function 'computeMaxCallStackSize' is different,the result is different too. What's the reason? Thanks very much!
Running environment:
node.js v6.9.1
OS:Win7
Its not the position but the order of execution which leads to this in the first function the statement
return computeMaxCallStackSize() + 1;
calls the increment first and then adds 1
return 1 + computeMaxCallStackSize();
If you try both return statements as same then it leads to same value. In later one as digit is first the js call stack exceeds overflow sooner as compared to first. The callstack value depends on the order of execution as in 2nd you change the order you get a lower value as recursion happens later.
You can also check by adding some console.log() or local variable call stack will decrease gradually with increase in execution statements.
If you try computeMaxCallStackSize() + 1; in both you will get same value.
What is the maximum recursion depth in Google Apps Script scripts? I have a function, match_recurse, which looks like the following pseudocode:
function match_recurse(array) {
for (i=0, i<3, i++) {
var arr2 = array.copy().push(i);
if (is_done(arr2)) break;
match_recurse(arr2);
}
}
(It also returns its results, but I don't want to bloat the question.)
Now, because the execution errored, the execution transcript and the logs were not saved, so I have no way of knowing whether my is_done function is doing its job wrong. I can do a few cases of the problem on paper and check recursion depth, but I don't know what the maximum is supposed to be.
Looking on the web, I saw an article mentioning that IE has a max call stack of 13 if you go through the Window object, but nothing else.
It is 1000, as one can see from here:
function recurse(i) {
var i = i || 1;
try {
recurse(i+1);
} catch (e) {
Logger.log(i);
}
}
The stack depth value is not documented. Executing the following code shows that this value is equal to 1000.
function getStackDepth(curvalue) {
try {
curvalue = getStackDepth(curvalue) + 1;
}
catch(err) {
}
return curvalue;
}
function test() {
var depth = getStackDepth(2);
debugger;
}