Fill an array recursively walking a DOM tree - javascript

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.

Related

Looking for matches in different arrays (Google Apps Script)

I have the following script in Google Apps Script:
for(var i=0; i<lastCode; i++) {
var productCode = prodCodesArr[i];
for(var j=0; j<kelliLastCode; j++) {
var kelliProductCode = kelliCodesArr[j];
if(productCode == kelliProductCode) {
Logger.log('match found')
}
}
}
The 2 arrays are created dynamically. So the idea is (and I know there must be MUCH better ways to do this, but I am pretty new to this so bear with me) that I am setting i to the value of the first product code in one array and then looping through the other array whilst storing the product codes in this one to j. Now, I tried logging:
Logger.log(productCode + ' - ' + kelliProductCode);
And this worked and indeed, there were instances where productCode and kelliProduct code matched.
Yet my if statement above does not pick these up.
Again, I'm sure I've botched this entirely but any help would be greatly appreciated...
What's the point of the check? To determine which of your prodCodesArr items are also in kelliCodesArr? Why not parse kelliCodesArr just once, and then use hash lookups instead of array traversal? This will mean that you don't have to use nested for loops, which will scale very poorly as the inner loop size grows. An example (with some checks for assumptions on my part):
function foo() {
const kelliCodes = getKelliCodesArraySomehow();
const productCodes = getProductCodesArraySomehow();
// If these are 2D arrays, note that for `var a = ['help']; var b = ['help'];`
// `a` is never equal to `b` because they are not the exact same object in memory.
if (kelliCodes.length && Array.isArray(kelliCodes[0])) {
throw new TypeError("This SO answer was predicated on `kelliCodes` and `productCodes` being 1D arrays, but they aren't!");
}
const kelliLookup = kelliCodes.reduce(function (obj, kpc, idx) {
if (typeof kpc === 'object') {
console.log({message: "This SO answer assumed kpc was a string", kpc: kpc});
throw new TypeError("You probably want to store a property of this object, not the whole object");
}
obj[kpc] = idx;
return obj;
}, {});
var productsAlsoInKelliCodes = productCodes.filter(function (pc) {
return kelliLookup.hasOwnProperty(pc);
});
productsAlsoInKelliCodes.forEach(function (pc) {
Logger.log("The index of this product code %s in kelliCodes is %s", pc, kelliLookup[pc]);
});
}
If your ___codes arrays are 2D arrays, you should flatten them before comparison, as comparing an Array instance to another Array instance will always return false, even if they contain the same element primitives--they aren't the exact same Array instance:
References
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
Array#forEach
Array#map
In JS, which is faster: Object's "in" operator or Array's indexof?
Javascript: what lookup is faster: array.indexOf vs object hash?
I'm sure there are more.
Something like this might help you to see what's happening:
function compareA(prodCodesArr,kelliCodesArr) {
var html="";
for(var i=0;i<prodCodesArr.length;i++) {
for(var j=0;j<kelliCodesArr.length;j++) {
if(productCodesArr[i]==kelliCodesArr[j]) {
html+=Utilities.formatString('Matched: %s=%s', productCodesArr[i],kelliCodesArr[j]);
}else{
html+=Utilities.formatString('No-Match: %s=%s', productCodesArr[i],kelliCodesArr[j]);
}
}
}
var userInterface=HtmlService.createHtmlOutput(html);
SpreadsheetApp.getUi().showModelessDialog(userInterface, 'Comparing')
}

Get path of object and convert it to string in JavaScript

How could I generate a string of an object's path? For example, say I want to turn the following path (not its contents) into a string:
object = grandparent.children[2].children[4].whatever;
I need a function like this:
function stringifyPath(obj) {
// somehow return "grandparent.children[2].children[4].whatever";
}
You simply can't do that in Javascript unless each object stores a reference to it's own parent and there's a known way to navigate children (like DOM nodes do).
This is because when you have an object embedded in another object (your parent/child relationship you speak of), it isn't really an object embedded in another. The child is an independent object and there's a reference to it in the parent. That same child could be stored in many different places. From Javascript's point of view, it doesn't actually have a parent. It's an object that many different things may have a reference to.
If each child object stored a reference to its own parent and there was a known way to walk children at any level, then it could be possible to write code to construct a path like you've said, but you'd have to be a lot more specific about what exactly these objects were and how you find out which child index a given object is.
For example, if these were DOM objects which meets both of the criteria (child contains reference to parent and there's a known way to navigate the children of any given object) and you wanted the root parent to be document.body, then you could do this:
function getSiblingPosition(obj) {
var siblings = obj.parentNode.children;
var elemCnt = 0;
for (var i = 0; i < siblings.length; i++){
if (siblings[i] === obj) {
return elemCnt;
} else {
if (siblings[i].nodeType === 1) {
++elemCnt;
}
}
}
}
function getPath(obj) {
var path = "";
while (obj && obj !== document.body) {
var cnt = getSiblingPosition(obj);
path = ".children[" + cnt + "]" + path;
obj = obj.parentNode;
}
path = "document.body" + path;
return path;
}
Working demo: https://jsfiddle.net/jfriend00/8w9v8kpf/

Reimplementing getElementsByClassName using Recursion - Javascript

I'm currently working on learning recursion and am trying to reimplement the getElementsByClassName function by walking the DOM using recursion. I finally feel like I've grasped the concepts but I'm having issues when I push the matching elements into a results array. Heres my code:
var getElementsByClassName = function(className){
var results = [];
var domWalker = function(node) {
for (var i=0; i<node.children.length; i++) {
if (node.children[i].classList.contains(className)) {
console.log(node.children[i])
results.push(node.children[i])
}
if (node.children[i].children.length > 0) {
domWalker(node.children[i])
}
}
return results;
};
domWalker(document.body);
console.log(results)
};
Basically, I need the results array to hold the matching elements it finds in HTML format like so:
[<div class="title"></div>, <button class="click"></button>]
... yet when I push these elements into my results array they change to the: [div.title, button.click] format.
I added the console.log statement above the call to results.push to see if the results appear in the proper format before they are pushed to the array which they do. The results being pushed to the array are the results I'm looking for, they just appear in the wrong format.
Why is push causing the format of my results to change and how can I get around this issue?
I solved this problem once upon a time. Although I haven't read through your solution, here is mine, heavily commented. I hope it helps:
var getElementsByClassName = function(className, node){
// The empty results array, which gets created whenever the function is
// called.
var results = [];
// Default the node to the document's body if it isn't set. This way, we can
// call the function recursively with child elements, but never have to
// worry about it the first time around.
node = node || document.body;
// If the node contains the class list in question, let's push it into the
// results array.
if (node.classList && node.classList.contains(className)) {
results.push(node);
}
// Now, let's fetch the child nodes of the current node.
var children = node.childNodes;
// If child nodes exist, then we proceed here.
if (children) {
// Let's now loop over all child nodes of the current node in question. This
// way, we'll be able to perform checks on each child node.
for (var i = 0; i < children.length; i++) {
// Fetch the i child node.
var child = children[i];
// At this point, we want to pass the child node back into the function,
// implementing recursion. The same checks above will occur, and if the
// node has the class name, it will be added to the results array from
// that function call.
//
// This returns an array, and we need to merge it with the current set
// of results, so we concat it.
//
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
results = results.concat(getElementsByClassName(className, child));
}
}
// We then return the combined results array from all the recursive function
// calls!
return results;
};
node.children[i] holds a reference to the HTML element
console.log() applies an implicit .toString() method giving what you see.
you need this additional code (to be extended to all possible tagNames you find):
var el = node.children[i];
el = '<' + el.tagName + (el.className ? ' class="' + el.className + '"': '') + '/>';
console.log(el);
results.push(el);

Javascript: recursion + for loop + scope

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.

Generic tree implementation in Javascript

Is anyone aware of a generic tree (nodes may have multiple children) implementation for JavaScript?
It should be able to do atleast these things,
get parent node.
get children nodes.
get all the descendants.
remove all the descendants.
remove children nodes.
Some implementation similar to Adjacency List Model.
Background: I needed JavaScript based hierarchical data storing for my webpage i could not find a good JavaScript implementation of generic trees so what i did is i used ajax to store hierarchical data into database using Adjacency List Model and php. The problem comes when the user is opening the same page in two tabs of same browser or opened the page in two different browsers because both the instances are writing to same table reading from same table which is causing me problems any possible work around for this also answers my question.
Edit: Performance is really not my constraint at any point of time i will not have more than 50 entries.
You can try this: https://github.com/afiore/arboreal
Or this: https://github.com/mauriciosantos/buckets/ (only Binary Searched Trees, but olso other data structures)
If you need anything more sophisticated, you will need to write your own library (or at least one object with all methods you desribed).
EDIT:
This is my simple code to achieve tree functionality. Remove all descendants and remove all children is in fact the same... so:
function Node(value) {
this.value = value;
this.children = [];
this.parent = null;
this.setParentNode = function(node) {
this.parent = node;
}
this.getParentNode = function() {
return this.parent;
}
this.addChild = function(node) {
node.setParentNode(this);
this.children[this.children.length] = node;
}
this.getChildren = function() {
return this.children;
}
this.removeChildren = function() {
this.children = [];
}
}
var root = new Node('root');
root.addChild(new Node('child 0'));
root.addChild(new Node('child 1'));
var children = root.getChildren();
for(var i = 0; i < children.length; i++) {
for(var j = 0; j < 5; j++) {
children[i].addChild(new Node('second level child ' + j));
}
}
console.log(root);
children[0].removeChildren();
console.log(root);
console.log(root.getParentNode());
console.log(children[1].getParentNode());
Run it in Chrome (or other browser which supports console).
Although you did say "generic tree", what your specific requirement sounds simple enough for an already built-in DOMParser.
I respect other programmers' opinions, but still I think you can give DOM a try and see if it fits you.
Here's an simple illustration on how it works:
var tXML="<root><fruit><name>apple</name><color>red</color></fruit><fruit><name>pear</name><color>yellow</color></fruit></root>";
var tree=(new DOMParser).parseFromString(tXML,"text/xml");
//get descendants
var childs=tree.documentElement.childNodes;
for(var i=0;i<childs.length;i++)
{
if(childs[i].nodeName=="fruit")
{
document.write(childs[i].getElementsByTagName("name")[0].textContent);
document.write(": ");
document.write(childs[i].getElementsByTagName("color")[0].textContent);
document.write("<br />");
}
}
//get child node
var appl=tree.documentElement.getElementsByTagName("fruit")[0];
document.write(appl.getElementsByTagName("name")[0].textContent+"<br />");
//get parent node
document.write(appl.parentNode.nodeName);
document.write("<br />");
//remove child node
if(tree.documentElement.getElementsByTagName("color").length>1)
{
var clr=tree.documentElement.getElementsByTagName("color")[1];
clr.parentNode.removeChild(clr);
}
document.write("<textarea>"+(new XMLSerializer).serializeToString(tree)+"</textarea><br />");
//remove descendants
while(tree.documentElement.childNodes.length>0)
{
tree.documentElement.removeChild(tree.documentElement.childNodes[0]);
}
document.write("<textarea>"+(new XMLSerializer).serializeToString(tree)+"</textarea>");
I didn't "simplified" those long function names, so you may get a better idea.

Categories