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.
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);
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.
Just for fun, I'm looking for a way to create a function, array, with the following behavior:
array() // []
array(2) // ugly function thing
array(2)() // [2]
array(2)(3)() // [2,3] etc
The closest I can come is
function array(x) {
if (x == null)
return []
return function() {
// same as above?!
// I don't want some inelegant solution involving a lot of additional parameters
}
}
Is there a way to do this in ECMA5? If not, prove that the syntax can't accomodate such a function.
Yes, "same as above". This is solved by a "recursive"1 call:
function makeArrayAppender(arr) {
return function array() {
var args = Array.prototype.slice.call(arguments);
if (!args.length)
return arr;
else
return makeArrayAppender(arr.concat(args));
};
}
var array = makeArrayAppender([]);
1: As the function is called from the returned "thunk" function, not from the call itself, it's not really recursive. It's more like a tail-call-optimised function, being invoked manually in-a-row without filling the stack
I think this should do exactly what you are looking for. The self-executing function scopes off r and rr, which are basically static variables using this implementation. Of course, you need to reset r after assigning it to rr, so you can return the Array when ra has an undefined argument, which then stops the recursive behavior.
var array = (function(){
var r = [], rr;
function ra(a){
if(a === undefined){
rr = r; r = []
return rr;
}
else{
r.push(a);
return ra;
}
}
return ra;
})();
console.log(array()); console.log(array(5)()); console.log(array());
console.log(array(7)(2)());
I looked at this:
Calling a JavaScript function named in a variable
But it doesn't answer my question.
This normally works:
window['class']['sub_class']['function_name'](data);
But now I'm trying to make a general function that can handle any depth:
function callbackFunction(callback, data){
//callback = a.b.c, or a.b, or a
callback = explode(callback);
//I need to be able to call callbackFunction and somehow callback and form their proper form below
window[callback.a](data);
//or
window[callback.a][callback.b](data);
//or
window[callback.a][callback.b][callback.c](data);
}
I believe the duplicate suggested by Bergi will only solve half of your problem. Since your final value will be a function, and since that function is a member of an object, you'll end up executing it in the wrong context (i.e., with the wrong this value).
I suggest you use something like this:
function getCallback(path) {
var arr = path.split('.');
var k;
var fn = window;
while(k = arr.shift()) {
if(typeof fn[k] === "function") {
fn = fn[k].bind(fn);
} else {
fn = fn[k];
}
}
if(typeof fn === "function") return fn;
return function(){};
}
http://jsfiddle.net/7CEd5/
Compare the value of this in the callback with what you get by using the answers to Convert string in dot notation to get the object reference.
You can chain references to objects/sub-objects/etc for however long you want. If you have a point-delimited string (e.g. "document.blah.blah2.method"), then you need to split it to individual tokens (e.g. ["document", "blah", "blah2", "method"]).
Then it's simply a matter of looping through the chain:
var c = window;
for (var i = 0; i < chain.length - 1; i++) {
c = c[chain[i]];
}
c[chain[chain.length-1]](some_arguments);
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.