Javascript: Recursively count number of children in a tree - javascript

We have a JSON tree structure which maintains the children of a specific node. This children parameter is either Undefined (no children) or an array.
I want to compute the score of a node, which is the sum of the scores of the children nodes. And a node without children has a score of one.
This seemed pretty straightforward to me, however only the last node from the leaf-nodes are updated correctly, the rest show as NaN. I'm not sure where Undefined/NaN is being introduced, I suspect it might be due to how JS handles references within recursion?
function updateScore(n) {
if (n.children == undefined) return 1;
n.children.forEach(function(c){
var r = updateScore(c);
//if (!r) console.log(n);
n.score += r;
});
}
updateScore(sparent);
The data is as follows:
var sparent = {"id":"src","score":0,"children":[{"id":0,"score":0,"children":[{"id":14,"score":0}]},{"id":2,"score":0},{"id":4,"score":0},{"id":11,"score":0},{"id":17,"score":0,"children":[{"id":31,"score":0}]},{"id":18,"score":0},{"id":23,"score":0,"children":[{"id":13,"score":0,"children":[{"id":10,"score":0},{"id":21,"score":0,"children":[{"id":16,"score":0}]},{"id":82,"score":0,"children":[{"id":22,"score":0},{"id":75,"score":0}]},{"id":91,"score":0}]},{"id":48,"score":0,"children":[{"id":70,"score":0,"children":[{"id":46,"score":0},{"id":74,"score":0}]},{"id":97,"score":0,"children":[{"id":25,"score":0}]}]}]},{"id":28,"score":0,"children":[{"id":85,"score":0}]},{"id":30,"score":0,"children":[{"id":24,"score":0,"children":[{"id":26,"score":0},{"id":53,"score":0,"children":[{"id":45,"score":0}]},{"id":76,"score":0,"children":[{"id":38,"score":0},{"id":62,"score":0},{"id":66,"score":0}]},{"id":93,"score":0,"children":[{"id":1,"score":0},{"id":56,"score":0}]}]},{"id":78,"score":0,"children":[{"id":50,"score":0,"children":[{"id":15,"score":0},{"id":67,"score":0}]}]},{"id":94,"score":0}]},{"id":36,"score":0},{"id":39,"score":0,"children":[{"id":35,"score":0,"children":[{"id":73,"score":0},{"id":83,"score":0,"children":[{"id":3,"score":0}]}]}]},{"id":41,"score":0,"children":[{"id":5,"score":0},{"id":32,"score":0}]},{"id":43,"score":0,"children":[{"id":20,"score":0,"children":[{"id":89,"score":0,"children":[{"id":52,"score":0}]}]},{"id":27,"score":0},{"id":86,"score":0,"children":[{"id":6,"score":0}]},{"id":98,"score":0,"children":[{"id":54,"score":0},{"id":59,"score":0},{"id":65,"score":0,"children":[{"id":8,"score":0},{"id":47,"score":0}]},{"id":79,"score":0}]}]},{"id":64,"score":0},{"id":71,"score":0,"children":[{"id":19,"score":0},{"id":51,"score":0,"children":[{"id":57,"score":0,"children":[{"id":29,"score":0,"children":[{"id":49,"score":0}]},{"id":60,"score":0,"children":[{"id":34,"score":0}]}]},{"id":61,"score":0,"children":[{"id":63,"score":0},{"id":72,"score":0,"children":[{"id":9,"score":0},{"id":37,"score":0}]}]},{"id":90,"score":0}]},{"id":92,"score":0,"children":[{"id":96,"score":0,"children":[{"id":55,"score":0,"children":[{"id":12,"score":0},{"id":69,"score":0}]},{"id":80,"score":0}]}]}]},{"id":77,"score":0,"children":[{"id":81,"score":0,"children":[{"id":40,"score":0}]}]},{"id":84,"score":0,"children":[{"id":7,"score":0,"children":[{"id":33,"score":0,"children":[{"id":42,"score":0},{"id":95,"score":0}]}]},{"id":44,"score":0,"children":[{"id":58,"score":0}]}]},{"id":87,"score":0,"children":[{"id":68,"score":0}]},{"id":88,"score":0},{"id":99,"score":0}]};

you forgot to return the score if the element has children. try this one:
function updateScore(n) {
if (n.children == undefined) return 1;
n.children.forEach(function(c){
var r = updateScore(c);
//if (!r) console.log(n);
n.score += r;
});
return n.score;
}

Related

Why is a recursive deep equality check faster than an iterative version in Javascript?

I noticed that all the deep equality implementations I've found are using recursion and theoretically the iterative form should be faster. However, it's a bit slower for me and I don't understand why.
Assume the data is the result of JSON.parse (i.e. primitives, plain objects, and arrays).
Recursive:
function equals1(x, y) {
if (x === y) return true;
if (Array.isArray(x) && Array.isArray(y)) {
if (x.length !== y.length) return false;
for (let i = 0; i < x.length; i++) {
if (!equals1(x[i], y[i])) return false;
}
return true;
}
if ((typeof x !== 'object') || (typeof y !== 'object')) return false;
const xKeys = Object.keys(x);
const yKeys = Object.keys(y);
if (xKeys.length !== yKeys.length) return false;
for (const k of xKeys) {
if (!y.hasOwnProperty(k)) return false;
if (!equals1(x[k], y[k])) return false;
}
return true;
}
Iterative:
function equals2(a, b) {
const stack = [a, b];
let idx = 2;
while (idx > 0) {
const x = stack[idx - 1];
const y = stack[idx - 2];
idx -= 2;
if (x === y) continue;
if (Array.isArray(x) && Array.isArray(y)) {
if (x.length !== y.length) return false;
for (let i = 0; i < x.length; i++) {
idx += 2;
if (idx > stack.length) stack.push(x[i], y[i]);
else {
stack[idx - 1] = x[i];
stack[idx - 2] = y[i];
}
}
} else {
if ((typeof x !== 'object') || (typeof y !== 'object')) return false;
const xKeys = Object.keys(x);
const yKeys = Object.keys(y);
if (xKeys.length !== yKeys.length) return false;
for (const k of xKeys) {
if (!y.hasOwnProperty(k)) return false;
idx += 2;
if (idx > stack.length) stack.push(x[k], y[k]);
else {
stack[idx - 1] = x[k];
stack[idx - 2] = y[k];
}
}
}
}
return true;
}
I'm using the index instead of the traditional stack.pop approach because it's slightly faster.
JSPerf: https://jsperf.com/deep-object-compare-123/1
The data is from Reddit: https://www.reddit.com/r/javascript.json
For me, the iterative version is 20-25% slower on Chrome and Edge, and the same speed on Firefox. I tried pre-allocating the stack array and removing the continue, but it didn't change the results. As far as I know, JS engines can optimize tail-recursive functions, but this isn't tail-recursive.
Any ideas what's going on?
A major difference between the two approaches is that your recursive function is doing a normal depth-first search for the first unequal value while your iterative function is putting all the children of an array/object onto the stack before searching into last child. This causes the stack array to grow much larger than the call stack of the recursive function will ever become, and it does quite some unnecessary copying of the entire data structure into a heterogenous array instead of keeping the values in local variables.
You do a single test on one single data structure, and from that you mean you can conclude that in general, recursive equality checks are faster than iterative ones? Are you serious? From that one single test, you can conclude nothing whatsoever. Neither could I conclude (anything much) of any one test where some iterative algorithm wins. There are sooooooooooooo many recursive and iterative ways to do deep equality testing (and other things), and there are sooooooooo many data structures. I did more than one test, and my results are, very decisively: INCONCLUSIVE, see below.
But first, one thing:
There is a little bug in your code: you don't properly check for the null case. If one argument is null, and the other one is a non-null object, an error will be thrown.
That can be easily fixed by adding the following line:
if ((x===null)||(y===null)) return false;
In equals1, put it right after if (x === y) return true;, and in equals2 put it after the continue. If both arguments were null, then the line before the inserted line does the right thing and makes the code not reach the inserted line, and if only one of the arguments is null, then the inserted line will take care of false being returned, instead of an error being thrown.
I have to admit that I find your iterative version very hard to read. I just can't understand it, but very much would like to. How does it work? Could you enlighten us, please? Does it use LIFO (last in first out) stack, corresponding to depth first search, or is it based on something different? I really would like to know.
I wrote another iterative version, based on your equals1, using a FIFO queue (first in first out, corresponding to breadth first search) - I find that very much easier to read.
And I added 3 test cases to jsperf.com, here they are:
redditData
linkedList1Knodes
linkedList10Knodes
All 3 tests use your equals1 and equals2 you quoted here (with the null bug fixed) and the FIFO version I wrote.
First test uses the original reddit data from your question, second uses a linked list with 1 thousand nodes, and third uses a linked list with 10 thousand nodes.
First test confirms that that your iterative version is about 20% slower than the recursive version, and my FIFO version is in between the two, at about 10% slower than the recursive version.
In the second test, your iterative version is the clear winner, it is much faster than the recursive version, and FIFO comes in last (it's a teeny wee bit slower than the the recursive).
In the third test, the recursive version crashes - stack overflow error, and again your iterative version is the winner (FIFO being about 30% slower)
Sorry I can't explain to you why all that is. A proper explanation probably would have to shed light onto many different aspects, I don't think there is a "singular elephant" explaining it all; maybe it makes sense to add more heterogeneous test cases, instead just one example from reddit (even if that is "real world"...)
And here is the FIFO iterative version
function equals_FIFO(x, y){
if (x===y) return true;
if ((x===null)||(y===null)||((typeof x)!=='object')||((typeof y)!=='object')) return false;
var xStack = [x], yStack = [y];
var currentIdx = 0;
var item1, item2, kid1, kid2, keys1, keys2, i, key;
while (currentIdx<xStack.length){
item1 = xStack[currentIdx];
item2 = yStack[currentIdx];
keys1 = Object.keys(item1);
keys2 = Object.keys(item2);
if (keys1.length!==keys2.length) return false;
for (i=0; i<keys1.length; i++){
key = keys1[i];
if (!item2.hasOwnProperty(key)) return false;
kid1 = item1[key];
kid2 = item2[key];
if (kid1!==kid2){
if ((kid1===null)||(kid2===null)||((typeof kid1)!=='object')||((typeof kid2)!=='object')) return false;
xStack.push(kid1);
yStack.push(kid2);
}
}
currentIdx++;
}
return true;
}

Trouble pushing to an array in JS

Below is just a section of my code but I know it's problematic because I can't get it to return any value except 'undefined'. I have been over this for hours and cannot figure it out.
I want to be able to input a number and have its factors pushed to an array. I have tested it by alerting the first item in the array and I get nothing. I'm sure this is a pretty easy but I just can't figure it out. Here is the code:
var numberInQuestion = prompt("Of what number are you wanting to find the largest prime factor?");
//determine factors and push to array for later use
var factorsArray = [];
function factors(numberInQuestion){
for(var i = 2; i < numberInQuestion-1; i++){
if(numberInQuestion % i === 0){
return factorsArray.push[i];
} else {
continue;
}
}
};
factors(numberInQuestion);
alert(factorsArray[0]);
Thanks for any help!
you can only return one value
you must use (), not [] for calling push
factorsArray should be local to factors (put the definition inside the function)
the else { continue; } is useless
Here is the fully corrected code:
var numberInQuestion = prompt("Of what number are you wanting to find the factors of?");
//determine factors
function factors(numberInQuestion){
var factorsArray = []; // make it local
for (var i = 2; i < numberInQuestion-1; i++){
if(numberInQuestion % i === 0){
factorsArray.push(i); // use (), and don't return here
} // no need for else { continue; } because it's a loop anyway
}
return factorsArray; // return at the end
};
var result = factors(numberInQuestion); // assign the result to a variable
alert(result);
Here's a JSFiddle.
You have an error in your pushing syntax. Correct syntax for pushing is -
factorsArray.push(i);
Also returning immediately from the function after finding the first divisor will not give you the full list. You probably want to return after you've found out all the divisors.
Taking all of the above into consideration, you should rewrite your function as follow -
function factors(numberInQuestion){
for(var i = 2; i < numberInQuestion - 1; i++){
if(numberInQuestion % i === 0) {
factorsArray.push(i);
}
}
}
and you will be OK.
You've coded this so that when you find the first factor your function returns immediately. Just get rid of the return keyword in that statement. (What "return" means in JavaScript and other similar languages is to immediately exit the function and resume from where the function was called.)
Oh, also, you call functions (like .push()) with parentheses, not square brackets.
The function should not return when pushing to the array. Return the array after executing the loop. The else clause is also unnecessary.
var numberInQuestion = prompt("Of what number are you wanting to find the largest prime factor?");
function factors(numberInQuestion){
var factorsArray = [];
for(var i = 2; i < numberInQuestion-1; i++){
if(numberInQuestion % i === 0 && isPrime(i)){
factorsArray.push(i);
}
}
return factorsArray;
};
var factors = factors(numberInQuestion);
alert(factors[factors.length-1]);
//From: http://stackoverflow.com/questions/11966520/how-to-find-prime-numbers
function isPrime (n)
{
if (n < 2) return false;
var q = Math.sqrt (n);
for (var i = 2; i <= q; i++)
{
if (n % i == 0)
{
return false;
}
}
return true;
}
Given the purpose of the example two items must be considered
The code does not determine if the number is actually prime. The code will return the smallest factor possible since the loop starts at two and increments, then returns the first element in the array. The largest factor would actually be the last element in the array. I have corrected the example to find the greatest prime factor. You can test it via this fiddle: http://jsfiddle.net/whKGB/1/

Recursive functions in Javascript and depth-tracking

I'm writing a recursive function in JS and having some trouble. Let's start with this very basic function:
function traverse(thing)
{
if (typeof traverse.depth == 'undefined')
traverse.depth = 1;
else
traverse.depth ++;
if (thing.child)
traverse(thing.child);
}
So this works fine, and depth acts as a static var of sorts, but the problem is that in a language like C that has proper static vars, as you back out of the function, this variable would (ostensibly) decrease, so it is a true depth. If I have three boxes that contain three boxes and each of those contain three boxes, etc., we're essentially drilling down into the deepest one till there are no children, then backing out up a level to a sibling, and traversing its children. The problem with the above code is that the depth keeps increasing and increasing infinitely, even though the TRUTH depth may only be 3 or 4 from the oldest ancestor to the youngest child. If there are 80 siblings on each level, that depth counter is going to skyrocket.
How do I keep track of true depth in JS recursive functions?
Don't attach the counter to the function. The counter is shared by all recursive calls, so the counter represents the number of function calls, instead of the recursion depth.
When depth is passed as a separate variable, the counter shows the true depth.
function traverse(thing, depth)
{
if (typeof depth == 'number')
depth++;
else
depth = 1;
if (thing.child)
traverse(thing, depth);
}
Another (and perhaps nicer) solution would be to utilize JS functional programming strengths and use a high-order function to keep all depth-related housekeeping outside of the main function. Consider, for example, the following classic example:
function fac(n) {
if(n < 3)
return n;
return n * fac(n - 1);
}
We want this one to break the recursion once its goes deeper than a given value. Let's code a wrapper:
function wrapDepth(fn, max) {
var depth = 0
return function() {
if (++depth > max)
throw "Too much recursion"
var out = fn.apply(null, [].slice.call(arguments, 0))
depth--;
return out;
}
}
Create a wrapper with max depth = 20:
fac = wrapDepth(fac, 20)
and test:
console.log(fac(10)) // 3628800
console.log(fac(100)) // Too much recursion
Note that we didn't make any change in the main function fac itself, but still, its recursion depth is now under control.
why don't you just modify the function signature to take a thing and an index? So you would call it like:
function traverse(thing, idx)
...
if (condition)
traverse(thing.child, ++idx)
Just add:
traverse.depth--;
Right before any return statements. So
if(x === 5)
return thing;
Would become:
if(x === 5){
traverse.depth--;
return thing;
}
And then add traverse.depth--; before your closing } of the function.
If I understood correctly, it looks to me like you want to track the depth of recursion.. but in your code you never decrement the depth as you finish 1 level in the recursion.
I tried out a simple code and I think this what you wanted,
DEMO
HTML:
<div id="result"></div>
JS:
var result = document.getElementById('result');
var tmpArray = [1,2,3,4,5,6,7,8];
function depthTest() {
if (typeof depthTest.depth == 'undefined')
depthTest.depth = 0;
else
depthTest.depth++;
result.innerHTML += depthTest.depth;
if (typeof tmpArray[depthTest.depth] != 'undefined')
depthTest();
result.innerHTML += depthTest.depth;
depthTest.depth--;
}
depthTest(tmpArray);
OUTPUT:
012345678876543210

javascript not removing undefined objects from array

I've got an in page text search using JS, which is here:
$.fn.eoTextSearch = function(pat) {
var out = []
var textNodes = function(n) {
if (!window['Node']) {
window.Node = new Object();
Node.ELEMENT_NODE = 1;
Node.ATTRIBUTE_NODE = 2;
Node.TEXT_NODE = 3;
Node.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
Node.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
Node.COMMENT_NODE = 8;
Node.DOCUMENT_NODE = 9;
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11;
Node.NOTATION_NODE = 12;
}
if (n.nodeType == Node.TEXT_NODE) {
var t = typeof pat == 'string' ?
n.nodeValue.indexOf(pat) != -1 :
pat.test(n.nodeValue);
if (t) {
out.push(n.parentNode)
}
}
else {
$.each(n.childNodes, function(a, b) {
textNodes(b)
})
}
}
this.each(function() {
textNodes(this)
})
return out
};
And I've got the ability to hide columns and rows in a table. When I submit a search and get the highlighted results, there would be in this case, the array length of the text nodes found would be 6, but there would only be 3 highlighted on the page. When you output the array to the console you get this:
So you get the 3 tags which I was expecting, but you see that the array is actually consisting of a [span,undefined,span,undefined,undefined,span]. Thus giving me the length of 6.
<span>
<span>
<span>
[span, undefined, span, undefined, undefined, span]
I don't know why it's not stripping out all of the undefined text nodes when I do the check for them. Here's what I've got for the function.
performTextSearch = function(currentObj){
if($.trim(currentObj.val()).length > 0){
var n = $("body").eoTextSearch($.trim(currentObj.val())),
recordTitle = "matches",
arrayRecheck = new Array(),
genericElemArray = new Array()
if(n.length == 1){
recordTitle = "match"
}
//check to see if we need to do a recount on the array length.
//if it's more than 0, then they're doing a compare and we need to strip out all of the text nodes that don't have a visible parent.
if($(".rows:checked").length > 0){
$.each(n,function(i,currElem){
if($(currElem).length != 0 && typeof currElem != 'undefined'){
if($(currElem).closest("tr").is(":visible") || $(currElem).is(":visible")){
//remove the element from the array
console.log(currElem)
arrayRecheck[i] = currElem
}
}
})
}
if(arrayRecheck.length > 0){
genericElemArray.push(arrayRecheck)
console.log(arrayRecheck)
}
else{
genericElemArray.push(n)
}
genericElemArray = genericElemArray[0]
$("#recordCount").text(genericElemArray.length + " " +recordTitle)
$(".searchResults").show()
for(var i = 0; i < genericElemArray.length; ++i){
void($(genericElemArray[i]).addClass("yellowBkgd").addClass("highLighted"))
}
}
else{
$(".highLighted").css("background","none")
}
}
If you look at the code below "//check to see if we need to do a recount on the array length. ", you'll see where I'm stripping out the text nodes based off of the display and whether or not the object is defined. I'm checking the length instead of undefined because the typeof == undefined wasn't working at all for some reason. Apparently, things are still slipping by though.
Any idea why I'm still getting undefined objects in the array?
My apologies for such a big post!
Thanks in advance
I've modified your eoTextSearch() function to remove dependencies on global variables in exchange for closures:
$.fn.extend({
// helper function
// recurses into a DOM object and calls a custom function for every descendant
eachDescendant: function (callback) {
for (var i=0, j=this.length; i<j; i++) {
callback.call(this[i]);
$.fn.eachDescendant.call(this[i].childNodes, callback);
}
return this;
},
// your text search function, revised
eoTextSearch: function () {
var text = document.createTextNode("test").textContent
? "textContent" : "innerText";
// the "matches" function uses an out param instead of a return value
var matches = function (pat, outArray) {
var isRe = typeof pat.test == "function";
return function() {
if (this.nodeType != 3) return; // ...text nodes only
if (isRe && pat.test(this[text]) || this[text].indexOf(pat) > -1) {
outArray.push(this.parentNode);
}
}
};
// this is the function that will *actually* become eoTextSearch()
return function (stringOrPattern) {
var result = $(); // start with an empty jQuery object
this.eachDescendant( matches(stringOrPattern, result) );
return result;
}
}() // <- instant calling is important here
});
And then you can do something like this:
$("body").eoTextSearch("foo").filter(function () {
return $(this).closest("tr").is(":visible");
});
To remove unwanted elements from the search result. No "recounting the array length" necessary. Or you use each() directly and decide within what to do.
I cannot entirely get my head around your code, but the most likely issue is that you are removing items from the array, but not shrinking the array afterwards. Simply removing items will return you "undefined", and will not collapse the array.
I would suggest that you do one of the following:
Copy the array to a new array, but only copying those items that are not undefined
Only use those array items that are not undefined.
I hope this is something of a help.
Found the answer in another post.
Remove empty elements from an array in Javascript
Ended up using the answer's second option and it worked alright.

Too much recursion

I'm using JS on firefox 4 and get the "too much recursion error" for the following code:
extractText: function(domObj) {
if (domObj == null) {
return "";
} else {
var acc = "";
if (domObj.nodeType == Node.TEXT_NODE) {
acc += domObj.nodeValue;
}
if (domObj.hasChildNodes()) {
var children = currentObj.childNodes;
for (var i = 0; i < children.length; i++) {
acc += sui.extractText(children[i]);
}
}
return acc;
}
}
};
Anyone?
I think that this line:
var children = currentObj.childNodes;
should be:
var children = domObj.childNodes;
It looks to me as if your reference to "currentObj" is starting over at the top instead of descending from the element under examination. It's hard to tell of course because you didn't include the relevant definition or initialization of "currentObj".
You can also try an iterative approach instead of recursion:
extractText: function(domObj) {
if (!(domObj instanceof Node)) return null;
var stack = [domObj], node, tf = [];
while (stack.length > 0) {
node = stack.pop();
switch (node.nodeType) {
case Node.TEXT_NODE:
tf.push(node.nodeValue);
break;
case Node.ELEMENT_NODE:
for (var i=node.childNodes.length-1; i>=0; i--)
stack.push(node.childNodes[i]);
break;
}
}
return tf.join("");
}
This algorithm implements a depth first search using a stack for the nodes that still must be visited. The first item on the stack is domObj if it’s a Node instance. Then for each node on the stack: if it is a Text node, its value is added to the text fragments array tf; if it’s an Element node, its child nodes are put on the stack in reverse order so that the first child is on top of the stack. These steps are repeated until the stack is empty. At the end, the text fragments in tf are put together using the array’s join method.

Categories

Resources