Javascript: recursion + for loop + scope - javascript

For the past few days I have tried to solve this problem, but up until now I have not been able to find a solution.
The code below recursively finds paths on a graph. Instead of outputting nodePath's with four nodes, it seems to output 'one nodePath' with a newly added node from every cycle (resulting in path's from 1 to 200+ nodes incrementally). The recursive path call does not seem to make a fresh 'nodePath', however it does with neighbors[node_nw] and depth.
var startNode = s.graph.nodes('n0');
var emptyNodeRoute = [];
path(startNode, 0, emptyNodeRoute);
function path (node, depth, nodePath) {
nodePath.push(node);
if (depth == 3) {
printPath (nodePath);
} else {
depth ++;
var neighbors = s.graph.neighbors(node.id);
for (var node_nw in neighbors) {
(function() {
path (neighbors[node_nw], depth, nodePath);
}());
}
}
}
//prints node route
function printPath (nodePath) {
var str = '';
for(var k = 0; k < nodePath.length; k++) {
str = str.concat(' ', nodePath[k].label);
}
console.log ('nodePath: ' + str);
}
I guess it has to do with the specificity's of javascript regarding (no) block scoping, closures and recursion? Or maybe something small I am overlooking? I have referenced several resources (amongst http://zef.me/2843/javascript-the-scope-pitfall) and topics on this site but none of them got me into solving this problem.
Any help would be greatly appreciated!

This is not an scoping, closure or recursion problem, but a reference problem.
You always call the path function with the same nodePath reference.
Copy the nodePath variable and everything works as expected.
Here is what you have to change:
for (var node_nw in neighbors) {
// the method slice makes a copy of the array
path (neighbors[node_nw], depth, nodePath.slice());
}
Have a look at the working jsFiddle demo.

Related

How is my breadth first search algorithm finding the shortest path in my tutorial example below

I understand the basics of how a breadth-first search works. It utilizes a queue data structure to find adjacent vertices level by level.
My issue is in understanding how to find the shortest path between two vertices. In the example below, we assign newly visited vertices along with their edge to an array called edgeTo, but my question is how is the data stored? Is it a two-dimensional array? And how is it retrieved with the pathTo function?
The for loop in the pathTo function looks a bit odd to me, certainly because I might be new to it. How does this get the shortest path and how is the data structured or how is it saving the edges?
// add this to Graph class
this.edgeTo = [];
// bfs function
function bfs(s) {
var queue = [];
this.marked[s] = true;
queue.push(s); // add to back of queue
while (queue.length > 0) {
var v = queue.shift(); // remove from front of queue
if (v == undefined) {
print("Visited vertex: " + v);
}
for each(var w in this.adj[v]) {
if (!this.marked[w]) {
this.edgeTo[w] = v;
this.marked[w] = true;
queue.push(w);
}
}
}
}
function pathTo(v) {
var source = 0;
if (!this.hasPathTo(v)) {
return undefined;
}
var path = [];
for (var i = v; i != source; i = this.edgeTo[i]) { // this for loop is new to me
path.push(i);
}
path.push(s);
return path;
}
function hasPathTo(v) {
return this.marked[v];
}
this.edgeTo is a simple array.
The BFS starts at the source vertex, and when it discovers a new vertex i, it sets edgeTo[i] to the predecessor vertex, which must necessarily be one step closer to the source.
In the pathTo function, the for loop follows the chain of edgeTo links from v back to the source. This enumerates the shortest path in reverse. These vertexes are appended to path as they are found.
pathTo then returns the path in reverse order, which is a little odd. A more usual implementation would reverse path before returning it, so that it would start at source and end at v. This function seems to be a little bugged in other ways, too. Maybe you're still working on it...

Fill an array recursively walking a DOM tree

I have to fill an array of strings as I walk a generic tree recursively from one node to all his children. In practice at each node that match from a node to a leaf, insert a string in the DOM tree.
I know it is a trivial problem but I could not solve.
This is the code that I wrote:
function operationsToInsert(node) {
var operations = [];
operationsToInsertRec(node, operations);
return operations;
}
function operationsToInsertRec(node, operations) {
var childNodes = node.childNodes;
operations.push("i(" + node.nodeName + ") ");
for(var i = 0; i < childNodes.length; i++) {
operationsToInsertRec(childNodes[i], operations);
operations.push("i(" + childNodes[i].nodeName + ")");
}
}
But there is the following error:
Uncaught TypeError: Cannot read property 'push' of undefined at line operations.push("insert(" + node.nodeName + ") ");
How can I fix?
Thanks
Here's a way to walk a tree using the handy Array.prototype.reduce function using a trick that lets it work on array-likes:
function flatten(ops, n) {
ops.push("i(" + n.nodeName + ") ");
if (n.childNodes && n.childNodes.length) {
[].reduce.call(n.childNodes, flatten, ops);
}
return ops;
}
var node = document.getElementById("start_here");
var ops = [node].reduce(flatten, []);
fiddle
The problem is you have only one function, and it isn't what you think it is! You redefined it, so when you call what you thought was the first one you provided only one argument, and the remaining arguments are implicitly undefined.
Here is your same code, reduced to a demonstratable example:
function operationsToInsert(node) {
console.log("Definition #1");
}
function operationsToInsert(node, operations) {
console.log("Definintion #2");
}
operationsToInsert();
You need to change the name of one of your functions so that you don't have a collision.
Edit to address new question:
I think you are saying that your new issue is most nodes appear in the list twice. Trace through the code and you'll see that you process every node except the root node twice. In operationsToInsertRec() you put the node in the list (childNodes[i]), then you pass it to operationsToInsertRec() where it puts it in the list (node).
Here is a simple change to address that:
function operationsToInsert(node) {
var operations = [];
operations.push("i(" + node.nodeName + ") ");
operationsToInsertRec(node, operations);
return operations;
}
function operationsToInsertRec(node, operations) {
var childNodes = node.childNodes;
for(var i = 0; i < childNodes.length; i++) {
operationsToInsertRec(childNodes[i], operations);
operations.push("i(" + childNodes[i].nodeName + ")");
}
}
In operationsToInsert() I push the root node. Then operationsToInsertRec() only handles the children, and so handles each node only once.
In a comment on a different answer I see you touch on the topic of traversal order. When traversing a tree, there are several different classifications of these algorithms: depth-first, which is subdivided into pre-order, in-order, and post-order, and breadth-first. You can find more information in the wikipedia article on tree traversal.

Is this as fast as JS array copy can get without loop unrolling?

This is related to this question.
I have heard that the while pattern with a decrement and a greater than test is faster than any other loop pattern. Given that, is this the fastest possible array copy in js?
function arrayCopy(src,sstart,dst,dstart,length) {
length += sstart;
dstart += length;
while(--length >= sstart) {
dst[--dstart] = src[length];
}
}
Other test functions
function slowCopy(src,sstart,dst,dstart,length) {
for(var i = sstart; i < sstart+length;i+=1 ) {
dst[dstart++] = src[i];
}
}
function aCopy(src,sstart,dst,dstart,length) {
Array.prototype.splice.apply(dst,[dstart, length].concat(src.slice(sstart,sstart+length)));
}
Test Results http://jsperf.com/fastest-js-arraycopy
arrayCopy -
2,899
±5.27%
fastest
slowCopy - WINNER
2,977
±4.86%
fastest
aCopy -
2,810
±4.61%
fastest
I want to add some more of the suggested functions below to the jsPerf tests but none of them incorporate source start offset, destination start offset or length of copy. Anyway, I was somewhat surprised by these results which appear to be the opposite of what I expect
Who says you need a loop?
var myArrayCopy = JSON.parse(JSON.stringify(myArray));
This method makes a deep clone of the array. Here it is in a function:
function arrayCopy(src,sstart,dst,dstart,length) {
dst = JSON.parse(JSON.stringify(src));
}
Keep in mind the other variables (besides src and dst) are there just to maintain your original code structure in case you have pre-existing calls to this function. They won't be used and can be removed.
Slow Copy is, surprisingly, the winner. By a narrow margin:
function slowCopy(src,sstart,dst,dstart,length) {
for(var i = sstart; i < sstart+length;i+=1 ) {
dst[dstart++] = src[i];
}
}
I think this is the fastest way:
var original = [1, 2, 3];
var copy = original.slice(0);

javascript map all paths

im not able to figure out the following problem. At point 1 I could either go to point 2 or point 5. From point to I could go to 3 or 4. From point 5 I could go to 6 or 7. From 7 there is only one path to 9. I would like to calculate all the full paths. Im not looking for the fastest route or anything. I need all the paths there are in a manner that I could follow them easily.
I have 2 questions:
Im not sure im using the correct way to 'store' the options (a[1]=[2,5]). Is this ok or is there a better way ?
Im not sure how to solve this. Can anyone give me a clue ? Im hoping im looking in the right direction :-)
The path:
1 ->2 ->3
->4
->5 ->6
->7 ->8 ->9
And the desired result:
1,2,3
1,2,4
1,5,6
1,5,7,8,9
My attempt at solving this in javascript
// this doesn't do what I need
var a = [];
a[1]=[2,5];
a[2]=[3,4];
a[5]=[6,7];
a[7]=[8];
a[8]=[9];
trytoloop(a,1);
function trytoloop(a,key){
if(a[key]){
for (var y in a[key]){
document.write(key);
trytoloop(a,a[key][y]);
}
} else {
document.write(key);
}
}
You do not keep track of the partial path you've built up so far. The array idea seems fine, though. Here's a working version with some more meaningful names: http://jsfiddle.net/YkH5b/.
// A cleaner way of defining the next keys
var nextKeysMap = {
1: [2, 5],
2: [3, 4],
5: [6, 7],
7: [8],
8: [9]
};
var fullPaths = [];
generateFullPaths([1], 1); // start off with partial path [1] and thus at key 1
function generateFullPaths(partialPath, currentKey) {
if(currentKey in nextKeysMap) { // can we go further?
var nextKeys = nextKeysMap[currentKey]; // all possible next keys
for (var i = 0; i < nextKeys.length; i++) { // loop over them
var nextKey = nextKeys[i];
// append the current key, and build the path further
generateFullPaths(partialPath.concat(nextKey), nextKey);
}
} else { // we cannot go further, so this is a full path
fullPaths.push(partialPath);
}
}
for(var i = 0; i < fullPaths.length; i++) {
console.log(fullPaths[i].join(","));
}
I put a solution below, but don't look if you don't want spoilers! It only deals with the case when there are no cycles in the graph.
Some hints without giving away the answer: I suggest that in your recursive function, you keep track of the whole path so far in the second argument, not just the current location, since otherwise you'll end up with a list of locations visited but how do you know the path that got you to each one? Second, in Javascript it's not considered good practice to iterate through an array with the for ... in construct, so you can use a regular for loop going from 0 to the length of the array. Thirdly, you're going to want to print the constructed path out at some point, but you don't want to do it at every step: instead, you want to print out the path when it's complete; that is, when there are no more places to go from the current location. Finally, I replaced document.write with console.log, since I believe document.write will overwrite the document contents each time you print something.
var a = [];
a[1]=[2,5];
a[2]=[3,4];
a[5]=[6,7];
a[7]=[8,9];
trytoloop(a,[1]);
function trytoloop(a,path){
var last = path[path.length - 1];
var next_hops = a[last];
// if there could be cycles, you might want to check that next_hops doesn't
// contain any already visited locations
if (next_hops) {
for (var i = 0; i < next_hops.length; i++) {
var new_path = path.concat([next_hops[i]]);
trytoloop(a, new_path);
}
} else {
console.log(path);
}
}
I haven't tried this, but off the top of my head, you could try something along these lines:
// use an object, and set the numbers as keys and the values as the child options
var a = {};
a[1] = {2:{3:{},4:{}},5:{6:{},7:{8:{9:{}}}}};
(function trytoloop(obj,num,str){
if(str.length>0) str += ',';
str += num
if(Object.keys(obj).length==0) document.writeln(str+'<br />');
else for(var key in obj) trytoloop(obj[key],key,str);
})(a[1],1,'');
Here's my solution: Fiddle
The final output it's going to the console, and you can change the initial node in here console.log(getPaths(1)); (starting from the node 1 according to your requirements).

How to prevent an infinite recursion

I am trying to recursively print out the contents of jQuery. I'm planning on using this to analyze an existing instance of jQuery (loaded in a page) against a small database of known "jQuery signatures", to determine what changes have been made (i.e. what plugins have been loaded, functions modified, etc.).
To do this, I have this small function:
function recurse(obj, iter){
var padding = (new Array(iter + 1)).join(" ") + ">";
for (var i in obj){
document.writeln(padding + i + "<br/>");
if (iter < 5)
recurse(obj[i], iter + 1);
}
}
When I execute this:
recurse(jQuery, 1);
I get something like this:
>prototype
>init
>prototype
>init
>prototype
>selector
>jquery
>0
.... On and on and on .....
My problem is, at the very beginning, you can see that prototype and then init repeat over and over again. The only reason it stopped at 5 deep is because of the if (iter < 5) check. If the limit wasn't there, it would have recurred [sic?] forever. The iteration limit helps, but what if there is a critical function 6 deep? Essentially, I have no idea what I should make this iteration limit, or if there should be one at all.
Instead, I'm thinking there must be some kind of algorithm that can prevent never-ending recursion. Does such an algorithm exist? Or should I change how I go about traversing jQuery? Thanks,
You could keep track of what values you've already seen, and just bail out when you see one again.
function recurse(obj) {
var marker = '__' + new Date().getTime() + '__';
function r(obj, iter) {
if (marker in obj) return;
var padding = (new Array(iter + 1)).join(" ") + ">";
obj[marker] = true;
for (var i in obj) {
if (!obj.hasOwnProperty(i) || i === marker) continue;
document.writeln(padding + i + "<br/>");
recurse(obj[i], iter + 1);
}
}
r(obj, 0);
}
Now this of course has the disadvantage of leaving your traversed object graph littered with extra properties, but for some applications that wouldn't be a problem. Also using the clock to make a "unique" marker is really lame; you might want to just use a counter, or maybe just a fixed nonsense string.
edit — Also, another issue (present in the original code too) is that this really should be checking to see if the "obj" values are really objects. If they're scalars, then there's no point doing anything, really. You'd just need to do a "typeof" check right after checking for the marker, and if you see null, a number, a string, or a boolean, just return.
Your recursion is missing a base case. See the definition of Recursion. You introduced an arbitrary base case of (depth < 5). Maybe instead use the length of the array, or as Pointy pointed out, the hasOwnProperty check to skip the recursive call.
Unless i am missing something, all you need to do is to skip strings (if you use a modern browser that allows indexing of strings, otherwise it doesn't matter) and the init function which is the jQuery self reference that gives you the infinite recursion
function recurse(obj, iter){
var padding = (new Array(iter + 1)).join(" ") + ">";
for (var i in obj){
document.writeln(padding + i + "<br/>");
if (i != 'init' && typeof obj[i] != 'string')
recurse(obj[i], iter + 1);
}
}
As several answers said: You algorithm must contain a proper break case to prevent infinite recursion.
But what happen if you cannot control the input of your algorithm?
For that case, or just for development purposes, you could use this:
function acmeRecursion(){
var count = localStorage.getItem("count");
if(count>50){
throw new Error("recursion limit exceeded");
}
count++;
//put your logic here
}
Building off of Pointy's answer (ideally this would be a comment, but alas, code doesn't work great in those), a better solution might be to just pass along an object to the the recurse function that will keep track of objects you've already seen. Something like this:
var recurse = function(obj)
{
var seen = {};
var inner = function(obj, padding)
{
for (var i in obj)
{
if (!(obj[i] in seen))
{
document.writeln(padding + i + '<br />');
seen[obj[i]] = true;
inner(obj[i], padding + ' ');
}
}
};
return inner(obj, '');
};
Which uses a closure rather than an argument to pass along the seen object, for the sake of simplicity, but the basic concept is the same.
This approach has the advantage of not adding extra attributes to the objects you're traversing.
Edit: I meant to explain this, but forgot. I'm not using hasOwnProperty here because in the case of printing an object graph, you probably do want to see inherited attributes.

Categories