Given a directed tree T with a variable number of children per node, I would like to find a path the size of PATH_SIZE of "good" nodes starting from root.
every node has an isGood() method and a getChildren() method that work as expected.
A simple DFS recursive solutions would look like this: (please correct me if I'm wrong)
function findGoodPath(node, depth){
if(!node.isGood()){
return null;
} else if (depth==PATH_SIZE){
return [node];
}
var children = node.getChildren();
for (var i=0; i<children.length; i++){
var result = findGoodPath(children[i], depth+1);
if (result){
return result.concat([node]);
}
}
return null;
}
Calling findGoodPath(root, 1) should find a result if one exists.
Now for the problem: thegetChildren() method of the node object is actually an async method that does I/O behind the scenes. it returns nothing and expects a single callback argument to handle returned children.
A modified code solution (which is WRONG) can look like this:
function findGoodPath(node, depth){
if(!node.isGood()){
return null;
} else if (depth==PATH_SIZE){
return [node];
}
node.getChildren(function(children){
for (var i=0; i<children.length; i++){
var result = findGoodPath(children[i], depth+1);
if (result){
return result.concat([node]);
}
}
});
}
This solution won't work: all the getChildren methods of a single node's children will be called at once, so it will actually perform a BFS. and worse, the return statements are associated with the anonymous callback function and will execute after the enclosing function has finished running.
It's clear that there is a need for some sort of a flow control mechanism. What is a simple and elegant solution for this problem?
UPDATE: I've accepted Sebastien's answer since it solves this problem with a recursion, which is how I presented the question. I've also posted an answer which uses the async's library whilst loop, this is what I ended up using. Sebastien was kind enough to benchmark these two methods here. (spoiler: performance is identical)
first, I think you have to call findGoodPath(children[i], depth + 1) if you want the depth equals the PATH_SIZE.
then, you do have a problem of closure. With your async call you always concat with a node instance wich is not the one you want.
One way you could do that could be :
node.getChildren((function(_node) {
return function(children){
for (var i=0; i<children.length; i++){
var result = findGoodPath(children[i], depth);
if (result){
return result.concat([_node]);
}
}
});
})(node));
But I think it's just a part of the problem as you're mixing sync function with async function.
The line:
var result = findGoodPath(children[i], depth)
is written as a sync call whereas findGoodPath is an async function, so it has to be written with callbacks too!
Hope it helps
ps: it would help to have a jsfiddle...
UPDATE : just a try. As I cannot test, it's not working, but it's the idea. I can't figure out if you need to create another scope in the second findGoodPath call, just as in the getChildren call
function findGoodPath(node, depth, callback){
if(!node.isGood()){
return callback(null);
} else if (depth==PATH_SIZE){
return callback([node]);
}
node.getChildren((function(_node, _callback) {
return function(children){
var node = _node, callback = _callback;
for (var i=0; i<children.length; i++){
findGoodPath(children[i], depth, function(result) {
if (result){
return callback(result.concat([node]));
}
});
}
});
})(node, callback));
}
I'm not 100% in focus now, but I am almost sure Async.js seriestasks is the right solution for you (If not seriestasks I'm willing to bet there is another control flow in Async.js that will do the trick.
OK, so there are several ways to achieve an async DFS traversal. Since async recursions have a tendency to become somewhat ugly, I've decided to get rid of the recursion.
I first re-implemented the synchronous version of the function using a while loop instead of a recursion:
function findGoodPathLoop(root){
var nodesToVisit = [{data: root, path:[]}];
while (nodesToVisit.length>0){
var currentNode = nodesToVisit.pop();
if (currentNode.data.isGood()){
var newPath = currentNode.path.concat(currentNode.data);
if (newPath.length==PATH_SIZE){
return newPath;
} else {
var childrenNodes = currentNode.data.getChildren().map(function(child){
return {data: child, path: newPath};
});
nodesToVisit = nodesToVisit.concat(childrenNodes);
}
}
}
return null;
}
Note: I saved the entire path for each node, this is not a necessity, you can just save the depth and maintain an array of the current path, though it's a bit messier.
I then used the async library to convert this function to an async one, replacing the standard while() function with async's whilst():
function findGoodPathAsync(root, pathCallback){
var result = null;
var nodesToVisit = [{data: root, path:[]}];
async.whilst(
function(){
return nodesToVisit.length>0 && result==null ;
},
function(next) {
var currentNode = nodesToVisit.pop();
if (currentNode.data.isGood()){
var newPath = currentNode.path.concat(currentNode);
if(newPath.length==PATH_SIZE){
result = newPath;
next();
} else {
currentNode.data.getChildren(function(children){
var childrenNodes = children.map(function(child){
return {data: child, path: newPath};
});
nodesToVisit = nodesToVisit.concat(childrenNodes);
next();
});
}
} else {
next();
}
},
function(err) {
//error first style callback
pathCallback(err, result);
}
);
}
Not a pretty one, but it's readable and it does the job.
Related
Code line in question:
callbackFn ? callbackFn(currentNode) : levelOrderList.push(currentNode.value);
I am having trouble of a way to think of this in psuedo-code terms since 'callbackFn' is used like a function but not defined like a function.
I know this code works and have ran it myself. I have also solved this without using the callbackFn, but I would really like to understand why this works.
My guess for psuedo cod would be:
if callbackFn exists (not null or undefined), then return callbackFn(currentNode).
else push currentNode.value to the levelOrderList.
Full code for context:
function levelOrder(callbackFn) {
const queue = [this.root];
const levelOrderList = [];
while (queue.length > 0) {
const currentNode = queue.shift();
callbackFn ? callbackFn(currentNode) : levelOrderList.push(currentNode.value);
const enqueueList = [
currentNode?.leftChild,
currentNode?.rightChild
].filter((value) => value);
queue.push(...enqueueList);
}
if (levelOrderList.length > 0) return levelOrderList;
}
Your guess for pseudo code is correct.
The author of that could should better have used an if...else structure like your pseudo code does. The conditional operator (? :) is used here as an unnecessary short-cut. Normally you would use the conditional operator to use the value that it evaluates to, like x = condition ? a : b;. But here that value is ignored. There is really no good reason to avoid if...else here.
The author added support for a callback mechanism as an alternative to returning an array. This doesn't look like best practice to me either. For two reasons:
This "polymorphism" can be confusing for the user of such an API. It is easier to understand when the two functionalities are offered by two different functions, one that returns the result in an array, another that calls the callback. The caller will choose the function based on how they want to deal with the traversed nodes.
A callback mechanism is rather "old style". It makes more sense to turn this function into a generator function. The caller can then easily decide what to do with the nodes that the returned iterator yields: collect those nodes in an array or just process them one by one.
This is how that generator function would look like:
class Node {
constructor(value) {
this.value = value;
this.leftChild = this.rightChild = null;
}
}
class Tree {
constructor(...values) {
this.root = null;
for (let value of values) this.add(value);
}
add(value) {
function addTo(node) {
if (!node) return new Node(value);
if (value < node.value) {
node.leftChild = addTo(node.leftChild);
} else {
node.rightChild = addTo(node.rightChild);
}
return node;
}
this.root = addTo(this.root);
}
*levelOrder() {
if (!this.root) return;
const queue = [this.root];
while (queue.length > 0) {
const currentNode = queue.shift();
yield currentNode.value;
if (currentNode.leftChild) queue.push(currentNode.leftChild);
if (currentNode.rightChild) queue.push(currentNode.rightChild);
}
}
}
// Demo
const tree = new Tree(4, 6, 7, 2, 1, 5, 3);
// Several ways to use the levelOrder generator function:
console.log(...tree.levelOrder());
console.log(Array.from(tree.levelOrder()));
for (let value of tree.levelOrder()) console.log(value);
I understand basic recursion, but this problem has be stumped. I have a tree structure set up in a database, where each node(row) has an id and parent id.
I need a function that can run and in the callback return an array of all the descendants of a particular node given its id.
I've been able to put together a function that can print out all of the values, but I can't figure out how to capture them and return them in the callback. I know the base case isn't set up correctly, as I'm not sure what it should even be.
I'd appreciate any help! Thank you!
// My "database"
var nodes_collection = [
{id:"id1",name:"name1",parentid:"."},
{id:"id2",name:"name2",parentid:"id1"},
{id:"id3",name:"name3",parentid:"id1"},
{id:"id4",name:"name4",parentid:"id2"},
{id:"id5",name:"name5",parentid:"id3"},
{id:"id6",name:"name6",parentid:"id3"},
{id:"id7",name:"name7",parentid:"id5"},
{id:"id8",name:"name8",parentid:"id7"},
{id:"id9",name:"name9",parentid:"id7"},
{id:"id10",name:"name10",parentid:"id9"},
];
// This is NOT a real function, it simply performs the function that the real getChildren does when connected to my database!!!
function getChildren(parentid, callback){
var children = [];
for(var i=0; i < nodes_collection.length; i++){
if(nodes_collection[i].parentid == parentid){
children.push(nodes_collection[i].id);
}
}
callback(children);
}
function allDescendants(parentid, callback) {
getChildren(parentid, function(childNodes) {
if (false) { // Only false because I don't know what my base case should be.
//console.log("done");
} else {
for (var i = 0; i < childNodes.length; i++) {
var child = childNodes[i];
allDescendants(child);
console.log(child); // Here it prints out all the values. How can I capture them? and return them with my callback?
}
}
});
}
allDescendants("id3", function(result){
console.log(result);
});
EDIT:
Due to some confusion, I've changed the code to a bare bones example of what I'm trying to do that can be run locally !!! getChildren() is NOT a real function, it simply performs the function that the real getChildren does when connected to my database!!!
Bottom line:
The code in question works to recursively touch all values. Now how can I store all the values that are currently being outputted via console.log()?
Here's one simple way. We create a result object and an intermediary recursive function, keeping allDescendants as a wrapper. When the recursion is complete, we return the result that now has all the descendants.
JsvaScript code:
// My "database"
var nodes_collection = [
{id:"id1",name:"name1",parentid:"."},
{id:"id2",name:"name2",parentid:"id1"},
{id:"id3",name:"name3",parentid:"id1"},
{id:"id4",name:"name4",parentid:"id2"},
{id:"id5",name:"name5",parentid:"id3"},
{id:"id6",name:"name6",parentid:"id3"},
{id:"id7",name:"name7",parentid:"id5"},
{id:"id8",name:"name8",parentid:"id7"},
{id:"id9",name:"name9",parentid:"id7"},
{id:"id10",name:"name10",parentid:"id9"},
];
// This is NOT a real function, it simply performs the function that the real getChildren does when connected to my database!!!
function getChildren(parentid, callback){
var children = [];
for(var i=0; i < nodes_collection.length; i++){
if(nodes_collection[i].parentid == parentid){
children.push(nodes_collection[i].id);
}
}
callback(children);
}
function allDescendants(parentid, callback) {
let result = [];
let go = function(children){
for (child of children){
result.push(child);
getChildren(child, go)
}
}
getChildren(parentid, go);
callback(result);
}
allDescendants("id3", function(result){
console.log('result: ' + JSON.stringify(result));
});
I propose this:
let obj = [
{id:"id1",name:"name1",parentid:"."},
{id:"id2",name:"name2",parentid:"id1"},
{id:"id3",name:"name3",parentid:"id1"},
{id:"id4",name:"name4",parentid:"id2"},
{id:"id5",name:"name5",parentid:"id3"},
]
function getChilds(obj, parent_id, callback) {
if(obj.length === 0) return;
else if (typeof callback !== 'function'){
throw new Error("The callback must be a function ");
return;
}
let childs = obj.filter(function (c) {return c.parentid == parent_id })
if(childs.length > 0 ){
childs = childs.map(function (c) {return c.id})
}
callback(childs)
}
// Test
getChilds(obj, "id1", function (childs) {console.log(childs)})
Please go through the below code.
I am not able to exactly what's calllback.call is doing.
Also, I am not able to know what is the difference between this and this[i] and how if(callback.call(this, this[i])) is evaluated to true or false.
Array.prototype.each = function(callback) {
var i = 0;
while (i < this.length) {
callback.call(this, this[i]);
i++;
}
return this;
};
Array.prototype.map = function(callback) {
var i = this.length;
var found = []
while (i--) {
if(callback.call(this, this[i])) {
found.push(this[i]);
}
}
return found;
};
The functions are called below:
Array.each(function(value){
...
})
Array.map(function(value){
...
})
I am not able to exactly what's calllback.call is doing.
Function.prototype.call lets you invoke a function with an explicit value for this
callback.call(this, this[i]);
is the same as
callback(this[i])
except that inside the callback the value of this is the set to be the same as it was in the current context.
Also am not able to know what is the difference between this and this[i].
In this context, this is the current array. So this means the whole array, this[i] gets the i'th element of the array.
The .each function you have will loop through an array and call a function for each of them. This is similar to javascript's built in Array.prototype.forEach.
The .map is named as though it were a polyfill for Array.protoype.map but is actually doing a .filter operation. Strange.
var arr = {'a':fn1,'b':fn2,'c':fn3}
$.each(arr,function(name,func){
(do something particular for the last iteration)
...
})
It'll be best if no additional variables are used.
EDIT:
I mean LITERALLY last one,which is the last pair I type them.
Your example variable is called 'arr', but it's not an array at all (it's an object). This makes it a little confusing.
When iterating over an object, there's no such thing as a "last" property, because the order of properties is undefined by design.
When iterating over an array, you can simply compare the first parameter of the callback with the (array.length-1) to detect the last iteration.
In code (for arrays):
var arr = [ "a","b","c" ];
$.each(arr, function(i,val) { if (i == arr.length-1) ... });
Philippe Leybaert's answer outlines the problems with your question very well, and there is probably a clearer way of doing what you want. But that said, I cannot see a way to do what you ask without using an extra variable.
var obj = { 'a': fn1, 'b': fn2, 'c': fn3 };
var lastKey;
$.each(obj, function(key, fn) {
// do stuff...
lastKey = key;
});
obj[lastKey].doStuffForLastIteration();
If you need something to happen, say you are iterating over a single list and you wanted another object to be inserted conditionally but if the condition is not met you need it to be inserted last, you can do something like:
$list = $({{some_selector}});
$list_elt = $({{some_html}})
$list.each(function (i) {
if ({{condition is met}}) {
$(this).before($list_elt);
return false;
}
else if (i == $list.length - 1) {
$(this).after($list_elt);
return false;
}
});
which is the same thing as Philippe's solution, really. If there is some reason this should not work, please comment and let me know, because I use it.
Here I propose a brand new, improved answer.
An elegant way could be using a after() function wrapper. Here's the code:
function after(fn, times){
return function(){
if(--times === 0){
var args = Array.prototype.slice.call(arguments);
fn.apply(this, args);
}
};
}
fn is the function you want to be executed at last, times is the number of different response you are waiting for.
after() wraps your function and creates a new function that runs its code only after times calls. Here's an example in brief:
function onLastResponse(foo){
console.log('this is last iteration');
}
var myCallback = after(onLastResponse, 3);
myCallback(); //not executed
myCallback(); //not executed
myCallback(); //executed
Check this jsbin for a live example: https://jsbin.com/sufaqowumo/edit?js,console
Now that I have seen your duplicate question - where you state, "For the following,it's 'c':fn3" - it seems you might be after the value of the maximum property of an object.
var obj = { 'a': fn1, 'b': fn2, 'c': fn3 };
var maxKey;
for (var key in arr) {
if (!(maxKey > key)) {
maxKey = key;
}
}
// fn will be fn3
var fn = obj[maxKey];
Being jQuery.each function syncronous, do you really need to track last iteration? Just put your code after the $.each() call.
I'm trying to extend the Array.push method so that using push will trigger a callback method and then perform the normal array function.
I'm not quite sure how to do this, but here's some code I've been playing with unsuccessfully.
arr = [];
arr.push = function(data){
//callback method goes here
this = Array.push(data);
return this.length;
}
arr.push('test');
Since push allows more than one element to be pushed, I use the arguments variable below to let the real push method have all arguments.
This solution only affects the arr variable:
arr.push = function () {
//Do what you want here...
return Array.prototype.push.apply(this, arguments);
}
This solution affects all arrays. I do not recommend that you do that.
Array.prototype.push = (function() {
var original = Array.prototype.push;
return function() {
//Do what you want here.
return original.apply(this, arguments);
};
})();
First you need subclass Array:
ES6 (https://kangax.github.io/compat-table/es6/):
class SortedArray extends Array {
constructor(...args) {
super(...args);
}
push() {
return super.push(arguments);
}
}
ES5 (proto is almost deprecated, but it is the only solution for now):
function SortedArray() {
var arr = [];
arr.push.apply(arr, arguments);
arr.__proto__ = SortedArray.prototype;
return arr;
}
SortedArray.prototype = Object.create(Array.prototype);
SortedArray.prototype.push = function() {
this.arr.push(arguments);
};
Array.prototype.push was introduced in JavaScript 1.2. It is really as simple as this:
Array.prototype.push = function() {
for( var i = 0, l = arguments.length; i < l; i++ ) this[this.length] = arguments[i];
return this.length;
};
You could always add something in the front of that.
You could do it this way:
arr = []
arr.push = function(data) {
alert(data); //callback
return Array.prototype.push.call(this, data);
}
If you're in a situation without call, you could also go for this solution:
arr.push = function(data) {
alert(data); //callback
//While unlikely, someone may be using "psh" to store something important
//So we save it.
var saved = this.psh;
this.psh = Array.prototype.push;
var ret = this.psh(data);
this.psh = saved;
return ret;
}
While I'm telling you how to do it, you might be better served with using a different method that performs the callback and then just calls push on the array rather than overriding push. You may end up with some unexpected side effects. For instance, push appears to be varadic (takes a variable number of arguments, like printf), and using the above would break that.
You'd need to do mess with _Arguments() and _ArgumentsLength() to properly override this function. I highly suggest against this route.
Or you could use "arguments", and that'd work too. I still advise against taking this route though.
There's another, more native method to achieve this: Proxy
const target = [];
const handler = {
set: function(array, index, value) {
// Call callback function here
// The default behavior to store the value
array[index] = value;
// Indicate success
return true;
}
};
const proxyArray = new Proxy(target, handler);
I wanted to call a function after the object has been pushed to the array, so I did the following:
myArray.push = function() {
Array.prototype.push.apply(this, arguments);
myFunction();
return myArray.length;
};
function myFunction() {
for (var i = 0; i < myArray.length; i++) {
//doSomething;
}
}