diff --git a/README.md b/README.md index 2523daa..56ad3b7 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This module generalizes structure traverse (*walking*). This is done via a `walk `getter(state, node, next, down, stop) -> state` - Recieves `state`, `node` and three control functions: `next`, `down` and `stop`, +- Called in a context (`this`), persistent within one `walk(..)` call, inherited from *walker*`.prototype` and usable to store data between `getter(..)` calls, - Can process `node` and `state`, - Can queue nodes for walking via `next(...nodes)` - Can walk nodes directly via `down(state, ...nodes) -> state` @@ -57,15 +58,17 @@ Target use-cases: ```javascript // check if structure contains 0... var containsZero = walk(function(r, e, next, down, stop){ + // NOTE: we'll only count leaf nodes... + this.nodes_visited = (this.nodes_visited || 0) return e === 0 ? - // target found, abort the search... - stop(true) + // target found, abort the search and report number of nodes visited... + stop(this.nodes_visited+1) : e instanceof Array ? // breadth-first walk... !!next(...e) - : r }, false) + : (this.nodes_visited++, r) }, false) - containsZero( [1, [2, 0], 4, [[5], 6]] ) // -> true + containsZero( [1, [2, 0], 4, [[5], 6]] ) // -> 3 containsZero( [1, [2, 5], 4, [[5], 6]] ) // -> false ``` See a more usefull search in [examples](#examples)... @@ -143,56 +146,56 @@ var sumd = walk(function(res, node, next, down, stop){ sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5 ``` +To explicitly see the paths the above take: +```javascript +var sum = walk(function(res, node, next){ + this.log(node) + return node instanceof Array ? + // compensate for that next(..) returns undefined... + next(...node) + || res + : res + node }, 0) +var sumd = walk(function(res, node, next, down, stop){ + this.log(node) + return node instanceof Array ? + down(res, ...node) + : res + node }, 0) + +sum.prototype.log = +sumd.prototype.log = +function(node){ + node instanceof Array + || console.log('-->', node) } + +sum([1, [2], 3, [[4, 5]]]) // -> 15 + +sumd([1, [2], 3, [[4, 5]]]) // -> 15 +``` + FInd first zero in tree and return it's path... ```javascript -// XXX res/path threading seem unnatural here... -var __firstZero = walk(function(res, node, next, down, stop){ - var k = node[0] - var v = node[1] - var path = res[0] - return v === 0 ? - // NOTE: we .slice(1) here to remove the initial null - // we added in firstZero(..)... - stop([ path.slice(1).concat([k]) ]) - : v instanceof Object? - down( - [path.concat([k]), null], - ...Object.entries(v)) - : res }, [[], null]) -var firstZero = function(value){ - // NOTE: we are wrapping the input here to make it the same - // format as that of Object.entries(..) items... - return __firstZero([null, value]).pop() } - -firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y'] -``` - - -FInd first zero in tree and return it's path... -```javascript -// same as the above but illustrates a different strategy, a bit -// cleaner but creates a walker every time it's called... -var firstZero = function(value){ - return walk( - function(res, node, next, down, stop){ - var k = node[0] - var v = node[1] - var path = res[0] - return v === 0 ? - // NOTE: we .slice(1) here to remove the initial null - // we added in firstZero(..)... - stop([ path.slice(1).concat([k]) ]) - : v instanceof Object? - down( - [path.concat([k]), null], - ...Object.entries(v)) - : res }, - [[], null], - // wrap the input to make it compatible with Object.entries(..) - // items... - [null, value]) - // separate the result from path... - .pop() } +// NOTE: the only reason this is wrapped into a function is that we need +// to restrict the number of items (L) this is passed to 1... +var firstZero = function(L){ + return walk(function(res, node, next, down, stop){ + // setup... + if(this.path == null){ + this.path = [] + node = [null, node] + } + var path = this.path + var k = node[0] + var v = node[1] + return v === 0 ? + // NOTE: we .slice(1) here to remove the initial null + // we added in firstZero(..)... + stop(path.slice(1).concat([k])) + : v instanceof Object? + (path.push(k), + down( + res, + ...Object.entries(v))) + : res}, false, L) } firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y'] ``` diff --git a/package.json b/package.json index 5ad0ae2..102e05a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "generic-walk", - "version": "0.0.3", + "version": "0.0.5", "description": "An extensible tree walk(..) framework...", "main": "walk.js", "scripts": { diff --git a/walk.js b/walk.js index ce93f4a..5416325 100644 --- a/walk.js +++ b/walk.js @@ -1,225 +1,107 @@ -/********************************************************************** -* -* -* -**********************************************************************/ -((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) -(function(require){ var module={} // make module AMD/node compatible... -/*********************************************************************/ - -// Walk... -// -// Construct a reusable walker function... -// walk(get(..)) -// -> func(state, ...nodes) -// -// Construct a reusable walker function with set initial state... -// walk(get(..), state) -// -> func(...nodes) -// -// Walk the nodes... -// walk(get(..), state, ...nodes) -// -> res -// -// The get(..) that walk(..) expects has the following signature: -// get(res, node, next(..), down(..), stop(..)) -// -> res -// -// Queue next nodes to walk... -// next(...nodes) -// -> undefined -// NOTE: this will just queue the nodes and return immediately. -// NOTE: the state is passed to the nodes implicitly, the get(..) -// handling first node from this list will get the last result -// returned by the last get(..) call on this level as it's -// state. -// -// Walk the next set of nodes... -// down(state, ...nodes) -// -> res -// NOTE: this will process all the nodes and then return the -// result. -// -// Stop the walk... -// stop() -// stop(result) -// -> undefined -// NOTE: this will break current function execution in a similar -// effect to 'return', i.e. no code in function will be -// executed after stop(..) call... -// NOTE: this is done by throwing a special error, this error -// should either not be caught or must be re-thrown, as -// shadowing this exception may have unexpected results... -// -// -// Example: -// // sum all the values of a nested array... -// var sum = walk(function(res, node, next){ -// return node instanceof Array ? -// // compensate for that next(..) returns undefined... -// next(...node) -// || res -// : res + node }, 0) -// -// // depth first walker... -// var sumd = walk(function(res, node, next, down, stop){ -// return node instanceof Array ? -// down(res, ...node) -// : res + node }, 0) -// -// sum([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 3, 2, 4, 5 -// sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5 -// -// -// Example: -// // XXX res/path threading seem unnatural here... -// var __firstZero = walk(function(res, node, next, down, stop){ -// var k = node[0] -// var v = node[1] -// var path = res[0] -// return v === 0 ? -// // NOTE: we .slice(1) here to remove the initial null -// // we added in firstZero(..)... -// stop([ path.slice(1).concat([k]) ]) -// : v instanceof Object? -// down( -// [path.concat([k]), null], -// ...Object.entries(v)) -// : res }, [[], null]) -// var firstZero = function(value){ -// // NOTE: we are wrapping the input here to make it the same -// // format as that of Object.entries(..) items... -// return __firstZero([null, value]).pop() } -// -// -// // same as the above but illustrates a different strategy, a bit -// // cleaner but creates a walker every time it's called... -// var firstZero = function(value){ -// return walk( -// function(res, node, next, down, stop){ -// var k = node[0] -// var v = node[1] -// var path = res[0] -// return v === 0 ? -// // NOTE: we .slice(1) here to remove the initial null -// // we added in firstZero(..)... -// stop([ path.slice(1).concat([k]) ]) -// : v instanceof Object? -// down( -// [path.concat([k]), null], -// ...Object.entries(v)) -// : res }, -// [[], null], -// // wrap the input to make it compatible with Object.entries(..) -// // items... -// [null, value]) -// // separate the result from path... -// .pop() } -// -// -// firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y'] -// -// -// -// NOTE: a walker is returned iff walk(..) is passed a single argument. -// NOTE: the following two are equivalent: -// walk(get, start, ...nodes) -// walk(get)(start, ...nodes) -// NOTE: walk goes breadth first... -// -// XXX might be a good idea to move this into it's own module... -// generic-walk might be a good name... -var walk = -module.walk = -function(get, state, ...nodes){ - var context = {} - // this is used to break out of the recursion... - // NOTE: this can leak out but we only care about it's identity thus - // no damage can be done... - var WalkStopException - var err_res - - var _step = function(nodes, res){ - // construct a comfortable env for the user and handle the - // results... - var _get = function(node){ - var next = [] - res = get.call(context, - res, - node, - // breadth first step... - // next(...nodes) -> undefined - function(...nodes){ next = nodes }, - // depth first step... - // down(state, ..nodes) -> res - function(res, ...nodes){ - return _step(nodes, res) }, - // stop... - // stop() - // stop(value) - // - // NOTE: 'throw' is used to stop all handling, including - // the rest of the current level... - function(r){ - err_res = r - WalkStopException = new Error('WALK_STOP_EVENT') - throw WalkStopException - }) - return next - } - - return nodes.length == 0 ? - // no new nodes to walk... - res - // do the next level... - // NOTE: see note below... ( ;) ) - : _step(nodes - .map(_get) - .reduce(function(next, e){ - return e instanceof Array ? - next.concat(e) - : next.push(e) }, []), res) - } - // _step(..) wrapper, handle WalkStopException... - var _walk = function(nodes, res){ - try{ - return _step(nodes, res) - - } catch(e){ - // handle the abort condition... - if(e === WalkStopException){ - return err_res - } - // something broke... - throw e - } - } - - return ( - // return a reusable walker... - arguments.length == 1 ? - // NOTE: this wrapper is here so as to isolate and re-order res - // construction and passing it to the next level... - // this is needed as when we do: - // step(res, ...nodes.map(_get).reduce(...)) - // res (if immutable) will first get passed by value and - // then will get re-assigned in _get(..), thus step(..) will - // not see the updated version... - // This approach simply pushes the pass-by-value of res till - // after it gets updated by _get(..). - function(res, ...nodes){ - return _walk(nodes, res) } - // reusable walker with a curried initial state... - // NOTE: better keep this immutable or clone this in get(..) - : arguments.length == 2 ? - function(...nodes){ - return _walk(nodes, state) } - // walk... - : _walk(nodes, state)) -} - - - - -/********************************************************************** -* vim:set ts=4 sw=4 : */ return module }) +/********************************************************************** +* +* +* +**********************************************************************/ +((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) +(function(require){ var module={} // make module AMD/node compatible... +/*********************************************************************/ + +// XXX might be a good idea to add a way to do something on start/end of +// walk... +// ...we can easily determine if this is the first call to getter(..) +// by checking and then setting an attr on the context... +var walk = +module.walk = +function(getter, state, ...nodes){ + var func + // this is used to break out of the recursion... + // NOTE: this can leak out but we only care about it's identity thus + // no damage can be done... + var WalkStopException + var err_res + + var _step = function(context, nodes, res){ + // construct a comfortable env for the user and handle the + // results... + var _get = function(node){ + var next = [] + res = getter.call(context, + res, + node, + // breadth first step... + // next(...nodes) -> undefined + function(...nodes){ next = nodes }, + // depth first step... + // down(state, ..nodes) -> res + function(res, ...nodes){ + return _step(context, nodes, res) }, + // stop walking... + // stop() + // stop(value) + // + // NOTE: 'throw' is used to stop all handling, including + // the rest of the current level... + function(r){ + err_res = r + WalkStopException = new Error('WALK_STOP_EVENT') + throw WalkStopException + }) + return next + } + + return nodes.length == 0 ? + // no new nodes to walk... + res + // do the next level... + // NOTE: see note below... ( ;) ) + : _step(context, nodes + .map(_get) + .reduce(function(next, e){ + return e instanceof Array ? + next.concat(e) + : next.push(e) }, []), res) + } + // _step(..) wrapper, handle WalkStopException and setup the initial + // context... + var _walk = function(nodes, res){ + try{ + return _step(func ? Object.create(func.prototype) : {}, nodes, res) + + } catch(e){ + // handle the abort condition... + if(e === WalkStopException){ + return err_res + } + // something broke... + throw e + } + } + + return ( + // return a reusable walker... + arguments.length == 1 ? + // NOTE: this wrapper is here so as to isolate and re-order res + // construction and passing it to the next level... + // this is needed as when we do: + // step(res, ...nodes.map(_get).reduce(...)) + // res (if immutable) will first get passed by value and + // then will get re-assigned in _get(..), thus step(..) will + // not see the updated version... + // This approach simply pushes the pass-by-value of res till + // after it gets updated by _get(..). + (func = function(state, ...nodes){ + return _walk(nodes, state) }) + // reusable walker with a curried initial state... + // NOTE: better keep this immutable or clone this in get(..) + : arguments.length == 2 ? + (func = function(...nodes){ + return _walk(nodes, state) }) + // walk... + : _walk(nodes, state)) +} + + + +/********************************************************************** +* vim:set ts=4 sw=4 : */ return module })