So I have a block of code that looks like this:
stack.forEach(function(element){
//sys.puts(sys.inspect(element, false, null));
console.log('-----element start-----');
console.log(element.type + ':' + element.raw);
console.log('-----element end-----');
if(element.children){
element.children.forEach(function(childElement){
stack.push(childElement);
});
}
});
The issue is that this is not behaving like I would except a stack to behave and wondering if this in an issue with JavaScript itself. The issue I am seeing if that when I call the stack.push for each child to add it to the stack for processing, the initial stack.forEach() does not seem to be picking it up and it is only logging to top level elements. If I try to do another stack.forEach() directly after this one, it then displays the next level of child elements so I know .push is diffidently adding the child elements to the stack.
It almost seems like .forEach() grabs the data as it is and does not get an updates if they happen within the foreach. Is this true for JavaScript? Is there a different way I can accomplish the same thing (be able to process a top level element and all levels of children elements below it)?
Your supposition is correct. The ForEach function takes a snapshot of the array and any updates will not be processed.
You are trying to implement a classic tree traversal algorithm which can most easily be coded as a recursive function as follows :
var stack = [];
function traverse (element) {
//sys.puts (sys.inspect(element, false, null));
console.log ('-----element start-----');
console.log (element.type + ':' + element.raw);
console.log ('-----element end-----');
stack.push (element); // for preorder traversal (parent before children)
if (element.children)
element.children.forEach (traverse);
// stack.push (element); // for postorder traversal (children before parent)
};
traverse (document.body); // for example
Yes, .forEach does only iterate over the range the array initially has, it works with a copy of the .length value. If you want to change that behavior, I'd recommend a classical loop which queries the length property each time:
var stack = […];
for (var i=0; i<stack.length; i++) {
// process stack[i]
stack.push(…);
}
or even a more list-like behavior with
var stack = […];
while (stack.length) {
var cur = stack.shift();
// process cur
stack.push(…); // with variable arguments, you want to stop somewhen :-)
}
If you don't want/need to maintain the stack yourself, a recursive function is the proper choice for tree traversal.
Related
I have the below code causing Internet Explorer to freeze. It's a project that involves processing student grades as an assignment:
var array1 = StudentGradeAreadHugeList();
var nextArrayItem = function() {
var grade = array1.pop();
if (grade) {
nextArrayItem();
}
};
i hope you can help me with this.
You could show more info about the application you're trying to do. But I believe it's a matter of stack overflow (maybe you're using a big list). So, to overcome that you should modify the "nextArrayItem":
window.setTimeout (nextArrayItem, 0)
The freeze incurring mainly from the big data, but now the Event Loop will handle the Recursion process and not your Call Stack.
This is likely caused by an endless recursion. Be aware of proper handling of return values in IE:
var array1 = StudentGradeAreadHugeList();
var nextArrayItem = function() {
var grade = array1.pop();
if ( grade !== null && typeof(grade) !== "undefined" ) {
nextArrayItem();
}
};
pop() on an empty array will not return boolean false but a typeless "undefined".
There's two problems here:
You might be exceeding the call stack limit
Your if-conditional is set-up incorrectly
For the first issue:
As one of the previous responders mentioned, if you have a very large list you can exceed the limit of the call stack since you need to do a recursive call for each element. While doing setTimeout might work, it feels like a hack-y solution. I think the real issue is that your function is handling the array recursively rather than iteratively. I would recommend re-writing your function using a for-loop.
For the second issue:
Let's say in this case your array was set to [100, 90, 80]. When you invoke nextArrayItem() it will work properly the first two time, but the third time you call nextArrayItem() you are popping off the last remaining item (in this case 100) and your grade will be set to 100 which is a truthy value. Therefore, your if-conditional will pass and your function erroneously try to invoke itself again despite the fact that your array is now empty and the program should now exit the call stack.
I tried testing your code using my example in Chrome and what happens is that it will recurse one too many times and invoke pop on an empty array, which will return undefined.
You can fix this issue by changing the if conditional to check for the last element in the array after you have popped the array.
See revised code:
var nextArrayItem = function() {
var grade = array1.pop();
if (array1[array1.length-1]) {
nextArrayItem();
}
};
I have a Javascript Array that holds the contents of a page. In order to draw all the objects in the right places when the page loads I loop over the array and pull out the elements. This worked very well until I allowed the objects to have children within them.
The current array structure is
0-> {elements=[] frame={I keep the frame attributes here with key value pairs}}, 1-> {elements=[] frame={}}
However, I just started adding sub-elements to the elements array in each object. So now I have to loop through/draw each element, check to see if there are any children and if so then I have to draw them too.
The problem I'm having is that after I loop through the first 0 object and it's children the for loop stops running. Is it because I'm calling the same function multiple times? I've done that before so I don't think that's what is happening.
this.run_loop = function (spawn, dom) {
console.log(spawn)
alert(spawn.length)
for (i = 0; i < spawn.length; i++) {
console.log(spawn[i])
//alert("i one")
var newdom = dom + "_" + i;
this.synthesize_elements(spawn[i], dom, newdom)
if (spawn[i].hasOwnProperty('elements')) {
//alert("FOUND")
var newarray = spawn[i]['elements'];
if (newarray.length > 0) {
this.run_loop(newarray, newdom)
}
}
}
}
This is a little old but I encountered a similar issue and found my way here, but I figured it out and thought I'd post the solution if anyone else came across this. The issue in my case (and it looks like in your case as well though I can't be sure) was in the declaration of the for loop:
for (i = 0; i < spawn.length; i++)
You're not declaring that i is a new var. so if anything inside the
this.run_loop(newarray, newdom)
function also manipulates a counter variable called i that isn't declared a new var, it will also change the one in your outer scope, and kick you out of the loop if it goes over the length of spawn
just always declare:
for (var i; i< spawn.length; i++)
in your loops or make sure your counters are unique.
Straight forward: How do I change the position of Kinetic.Group children in the array? It is really important for me that I can alter the array and the position of each children.
I tried to add a child with Array.splice on a specific position but the library crashed. So I sticked to the native .add() function, took the child from the end of the array and spliced it on first place afterwards:
mapTilesContainer.add(image);
var tempChild = mapTilesContainer.children[mapTilesContainer.children.length - 1];
// delete it from the last place
mapTilesContainer.children.splice(mapTilesContainer.children.length - 1, 1);
// add it to the first place
mapTilesContainer.children.splice(0, 0, tempChild);
which somehow works, but if I then want to destroy a child regularly with .destroy() it crashes again:
mapTilesContainer.children[8].destroy();
Telling me: Uncaught TypeError: Cannot call method 'getLayer' of undefined in kinetic.js:2
Adding and destroying without messing with splice works though. Any ideas?
--- Very uncommon requirement ---
Is there no workaround in your code instead of messing with kinetic's internal arrays?
It's almost always a bad idea to mess with the internals of a library script
Having said that, I took a quick look at the kinetic's Container.js code.
https://github.com/ericdrowell/KineticJS/blob/master/src/Container.js
This is the internal add child method:
add: function(child) {
var children = this.children;
this._validateAdd(child);
child.index = children.length;
child.parent = this;
children.push(child);
this._fire('add', {
child: child
});
// chainable
return this;
},
Note that child.index property is set to the position of the child in the array of children.
Properly resetting each child.index is probably your key to manipulating Kinetic's children array.
At line 237 of Container.js is this internal method:
_setChildrenIndices: function() {
var children = this.children, len = children.length;
for(var n = 0; n < len; n++) {
children[n].index = n;
}
},
This method might be used by kinetic to reset the indices of a children array.
If you shuffle the children array and then execute your own version of _setChildrenIndex, you might not blow up your own app.
Again:
It's almost always a bad idea to mess with the internals of a library script
This has been a challenge for me...
I have a set of nodes in an XML doc. I need to sort them based on a certain node value. So if I iterate through the nodes, and then the node value matches my criteria, I want it to go to the end.
Problem is, of course as soon as I swap, as nodes are in a live set, the iteration pointer misses one entry of course, as the appendChild is operating on a live-set.
This is my code so far, but as I said, it may miss an entry due to the swapping:
for (var i=1; i <= nElem; i++)
{
var node = getNode(dom,"//item[" + i + "]");
var state = getNodeValue(dom,"//item[" + i + "]/state");
if ((state != 'XX') && (i != nElem))
{
node.parentNode.appendChild(node);
}
}
What I actually want is that all items in state "XX" are at the top.
Has anyone an intelligent idea to this?
Thanks
You could use array.sort() and pass a custom sort routine:
var nodes = getNode(dom, "//item"); gets you an array of items
next, remove the entries in nodes from the dom
do an nodes.sort(sortfunction) where sortfunction is sortfunction(a,b)
implement sortfunction so that it returns
-1 if a shall be lower than b
0 if equal
1 if a shall be higher than b
add the entries of nodes back to the dom
I think, that would do it (as long as I'm not missing something).
I have a connected, directed, cyclic graph. The task is to discover every single node in the graph without falling into an infinite loop, as a regular tree traversal algorithm will do.
You can assume that I already know what node to start at so as to reach all points in the directed graph, and that for each node I have a function that will return the nodes it directs to. Is there a known algorithm for finding all nodes?
The main issue is really avoiding cycles, and I would love it if there was a way to do that without keeping track of every single node and comparing it with a list of nodes that has already been traversed.
If you need more details, the actual task is to get a list of every named function in JavaScript, including functions that are properties of other objects. So I tried something like the following, as I thought the JS objects' references to each other made a tree (but of course it doesn't):
function __findFunctions(obj){
for (var f in obj){
// for special case of edge with self
if (obj === obj[f]){
continue
}
if (typeof obj[f] === 'function' &&
obj.hasOwnProperty(f) &&
// exclude native functions, we only want user-defined ones
!(/\[(native\scode|object\sfunction)\]/i).test(obj[f].toString()) &&
// exclude functions with __ prefix
!(/^\s*function\s*__/).test(obj[f].toString())
){
document.write(f + "<br/>" + obj[f].toString() + "<hr/>");
}
//alert(typeof obj[f] + "\n" + obj + "\n" + obj[f] + "\n" + f)
__findFunctions(obj[f]);
}
}
__findFunctions(window);
The problem with this code is that it gets stuck in cycles.
I would love it if there was a way to do that without keeping track of every single node and comparing it with a list of nodes that has already been traversed.
It may not be as bad as checking a list of already-traversed nodes. You could, instead, give each node a unique ID of some sort:
// psuedo
id=0;
for each node
node.id = id++;
etc.
Then you can add each node's ID to a hash while you traverse:
var alreadyTraversed = {};
// Traversing a node:
alreadyTraversed[node.id] = true;
And later on, when you need to check whether or not its already been traversed:
if (node.id in alreadyTraversed) // It's already been traversed...
Or, for a really rudimentary solution, simply set some property on each object that you traverse:
node._traversed = true;
// Later:
if (someNode._traversed) // already traversed.
You would need to maintain a list of already visited nodes if you want to avoid cycles.
E.g.
__findFunctions(obj, visited) as your signature
at start do an "in array" test for current obj and return if so.
at start add obj to the visited for subsequent traversals.