diff --git a/README.md b/README.md index 5f3c48e..43d36f5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,187 @@ # walk.js + An extensible tree walk(..) framework... + +## Theory and operation + +This module generalizes structure traverse (*walking*). This is done via a `walk(..)` function that recieves a user-defined `getter(..)` and returns a *walker*. + +`walk(getter)(state, ...nodes) -> state` +`walk(getter, state)(...nodes) -> state` +`walk(getter, state, ...nodes) -> state` +- Recieves a `getter` function a `state` and a list of `nodes`, +- Iterates through `nodes` calling the `getter(..)` per node, threading the `state` through each call, +- Returns the `state` when there are no more `nodes`. + +`getter(state, node, next, down, stop) -> state` +- Recieves `state`, `node` and three control functions: `next`, `down` and `stop`, +- Can process `node` and `state`, +- Can queue nodes for walking via `next(...nodes)` +- Can walk nodes directly via `down(state, ...nodes) -> state` +- Can abbort *walking* and return a state via `stop()` or `stop(state)` +- Returns `state`, + +A trivial *flat* example... +```javascript +walk(function(r, n){ return r+n }, 0, ...[1,2,3]) // -> 6 +``` + +The above is essentially equivalent to... +```javascript +[1,2,3].reduce(function(r, n){ return r+n }, 0) // -> 6 +``` + +And for trivial or *flat* lists `.reduce(..)` and friends are simpler and more logical. + +Target use-cases: +- The input is not *flat*: + ```javascript + var sum = walk(function(r, n){ + return n instanceof Array ? + down(r, ...n) + : r + n }, 0) + + sum( [1, [2, 3], 4, [[5], 6]] ) // -> 21 + ``` +- Need to abort the recursion prematurelly: + ```javascript + var containsZero = walk(function(r, e, next, down, stop){ + return e === 0 ? + // target found, abort the search... + stop(true) + : e instanceof Array ? + !!next(...e) + : r }, false) + + containsZero( [1, [2, 0], 4, [[5], 6]] ) // -> true + containsZero( [1, [2, 5], 4, [[5], 6]] ) // -> false + ``` + + +## Installation and loading + +```shell +$ npm install --save generic-walk +``` + +```javascript +var walk = require('generic-walk').walk +``` + +*Note: This module supports both AMD and node's `require(..)`** + + +## API + +`walk(getter(..)) -> walker(state, ...nodes)` +Construct a reusable walker. + + +`walk(getter(..), state) -> walker(...nodes)` +Construct a reusable walker with fixed initial state. + + +`walk(getter(..), state, ...nodes) -> result` +Walk the nodes. + + +### The getter + +`getter(state, node, next(..), down(..), stop(..)) -> state` +User provided function, called to process a node. + + +`next(...nodes)` +Queue `nodes` for walking. The queued nodes will get *walked* after this level of nodes is done (i.e. the `getter(..)` is called for each node on level). + + +`down(state, ...nodes) -> state` +Walk `nodes` and return `state`. The nodes will get *walked* immidiately. + + +`stop()` +`stop(state)` +Stop walking and return `state`. The passed `state` is directly returned from the *walker*. + +*Note that `stop(..)` behaves in a similar manner to `return`, i.e. execution is aborted immidiately.* + + +## Examples + +Sum all the values of a nested array (breadth-first)... +```javascript +var sum = walk(function(res, node, next){ + return node instanceof Array ? + // compensate for that next(..) returns undefined... + next(...node) + || res + : res + node }, 0) + +sum([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 3, 2, 4, 5 +``` + +Sum all the values of a nested array (depth-first)... +```javascript +var sumd = walk(function(res, node, next, down, stop){ + return node instanceof Array ? + down(res, ...node) + : res + node }, 0) + +sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5 +``` + +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() } + +firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y'] +``` + +