mirror of
https://github.com/flynx/walk.js.git
synced 2025-10-29 19:10:11 +00:00
tweaking and cleanup...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
ba2ba57287
commit
aa2343aa93
107
README.md
107
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`
|
`getter(state, node, next, down, stop) -> state`
|
||||||
- Recieves `state`, `node` and three control functions: `next`, `down` and `stop`,
|
- 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 process `node` and `state`,
|
||||||
- Can queue nodes for walking via `next(...nodes)`
|
- Can queue nodes for walking via `next(...nodes)`
|
||||||
- Can walk nodes directly via `down(state, ...nodes) -> state`
|
- Can walk nodes directly via `down(state, ...nodes) -> state`
|
||||||
@ -57,15 +58,17 @@ Target use-cases:
|
|||||||
```javascript
|
```javascript
|
||||||
// check if structure contains 0...
|
// check if structure contains 0...
|
||||||
var containsZero = walk(function(r, e, next, down, stop){
|
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 ?
|
return e === 0 ?
|
||||||
// target found, abort the search...
|
// target found, abort the search and report number of nodes visited...
|
||||||
stop(true)
|
stop(this.nodes_visited+1)
|
||||||
: e instanceof Array ?
|
: e instanceof Array ?
|
||||||
// breadth-first walk...
|
// breadth-first walk...
|
||||||
!!next(...e)
|
!!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
|
containsZero( [1, [2, 5], 4, [[5], 6]] ) // -> false
|
||||||
```
|
```
|
||||||
See a more usefull search in [examples](#examples)...
|
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
|
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...
|
FInd first zero in tree and return it's path...
|
||||||
```javascript
|
```javascript
|
||||||
// XXX res/path threading seem unnatural here...
|
// NOTE: the only reason this is wrapped into a function is that we need
|
||||||
var __firstZero = walk(function(res, node, next, down, stop){
|
// to restrict the number of items (L) this is passed to 1...
|
||||||
var k = node[0]
|
var firstZero = function(L){
|
||||||
var v = node[1]
|
return walk(function(res, node, next, down, stop){
|
||||||
var path = res[0]
|
// setup...
|
||||||
return v === 0 ?
|
if(this.path == null){
|
||||||
// NOTE: we .slice(1) here to remove the initial null
|
this.path = []
|
||||||
// we added in firstZero(..)...
|
node = [null, node]
|
||||||
stop([ path.slice(1).concat([k]) ])
|
}
|
||||||
: v instanceof Object?
|
var path = this.path
|
||||||
down(
|
var k = node[0]
|
||||||
[path.concat([k]), null],
|
var v = node[1]
|
||||||
...Object.entries(v))
|
return v === 0 ?
|
||||||
: res }, [[], null])
|
// NOTE: we .slice(1) here to remove the initial null
|
||||||
var firstZero = function(value){
|
// we added in firstZero(..)...
|
||||||
// NOTE: we are wrapping the input here to make it the same
|
stop(path.slice(1).concat([k]))
|
||||||
// format as that of Object.entries(..) items...
|
: v instanceof Object?
|
||||||
return __firstZero([null, value]).pop() }
|
(path.push(k),
|
||||||
|
down(
|
||||||
firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y']
|
res,
|
||||||
```
|
...Object.entries(v)))
|
||||||
|
: res}, false, L) }
|
||||||
|
|
||||||
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']
|
firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y']
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "generic-walk",
|
"name": "generic-walk",
|
||||||
"version": "0.0.3",
|
"version": "0.0.5",
|
||||||
"description": "An extensible tree walk(..) framework...",
|
"description": "An extensible tree walk(..) framework...",
|
||||||
"main": "walk.js",
|
"main": "walk.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
154
walk.js
154
walk.js
@ -7,144 +7,26 @@
|
|||||||
(function(require){ var module={} // make module AMD/node compatible...
|
(function(require){ var module={} // make module AMD/node compatible...
|
||||||
/*********************************************************************/
|
/*********************************************************************/
|
||||||
|
|
||||||
// Walk...
|
// XXX might be a good idea to add a way to do something on start/end of
|
||||||
//
|
// walk...
|
||||||
// Construct a reusable walker function...
|
// ...we can easily determine if this is the first call to getter(..)
|
||||||
// walk(get(..))
|
// by checking and then setting an attr on the context...
|
||||||
// -> 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 =
|
var walk =
|
||||||
module.walk =
|
module.walk =
|
||||||
function(get, state, ...nodes){
|
function(getter, state, ...nodes){
|
||||||
var context = {}
|
var func
|
||||||
// this is used to break out of the recursion...
|
// this is used to break out of the recursion...
|
||||||
// NOTE: this can leak out but we only care about it's identity thus
|
// NOTE: this can leak out but we only care about it's identity thus
|
||||||
// no damage can be done...
|
// no damage can be done...
|
||||||
var WalkStopException
|
var WalkStopException
|
||||||
var err_res
|
var err_res
|
||||||
|
|
||||||
var _step = function(nodes, res){
|
var _step = function(context, nodes, res){
|
||||||
// construct a comfortable env for the user and handle the
|
// construct a comfortable env for the user and handle the
|
||||||
// results...
|
// results...
|
||||||
var _get = function(node){
|
var _get = function(node){
|
||||||
var next = []
|
var next = []
|
||||||
res = get.call(context,
|
res = getter.call(context,
|
||||||
res,
|
res,
|
||||||
node,
|
node,
|
||||||
// breadth first step...
|
// breadth first step...
|
||||||
@ -153,8 +35,8 @@ function(get, state, ...nodes){
|
|||||||
// depth first step...
|
// depth first step...
|
||||||
// down(state, ..nodes) -> res
|
// down(state, ..nodes) -> res
|
||||||
function(res, ...nodes){
|
function(res, ...nodes){
|
||||||
return _step(nodes, res) },
|
return _step(context, nodes, res) },
|
||||||
// stop...
|
// stop walking...
|
||||||
// stop()
|
// stop()
|
||||||
// stop(value)
|
// stop(value)
|
||||||
//
|
//
|
||||||
@ -173,17 +55,18 @@ function(get, state, ...nodes){
|
|||||||
res
|
res
|
||||||
// do the next level...
|
// do the next level...
|
||||||
// NOTE: see note below... ( ;) )
|
// NOTE: see note below... ( ;) )
|
||||||
: _step(nodes
|
: _step(context, nodes
|
||||||
.map(_get)
|
.map(_get)
|
||||||
.reduce(function(next, e){
|
.reduce(function(next, e){
|
||||||
return e instanceof Array ?
|
return e instanceof Array ?
|
||||||
next.concat(e)
|
next.concat(e)
|
||||||
: next.push(e) }, []), res)
|
: next.push(e) }, []), res)
|
||||||
}
|
}
|
||||||
// _step(..) wrapper, handle WalkStopException...
|
// _step(..) wrapper, handle WalkStopException and setup the initial
|
||||||
|
// context...
|
||||||
var _walk = function(nodes, res){
|
var _walk = function(nodes, res){
|
||||||
try{
|
try{
|
||||||
return _step(nodes, res)
|
return _step(func ? Object.create(func.prototype) : {}, nodes, res)
|
||||||
|
|
||||||
} catch(e){
|
} catch(e){
|
||||||
// handle the abort condition...
|
// handle the abort condition...
|
||||||
@ -207,19 +90,18 @@ function(get, state, ...nodes){
|
|||||||
// not see the updated version...
|
// not see the updated version...
|
||||||
// This approach simply pushes the pass-by-value of res till
|
// This approach simply pushes the pass-by-value of res till
|
||||||
// after it gets updated by _get(..).
|
// after it gets updated by _get(..).
|
||||||
function(res, ...nodes){
|
(func = function(state, ...nodes){
|
||||||
return _walk(nodes, res) }
|
return _walk(nodes, state) })
|
||||||
// reusable walker with a curried initial state...
|
// reusable walker with a curried initial state...
|
||||||
// NOTE: better keep this immutable or clone this in get(..)
|
// NOTE: better keep this immutable or clone this in get(..)
|
||||||
: arguments.length == 2 ?
|
: arguments.length == 2 ?
|
||||||
function(...nodes){
|
(func = function(...nodes){
|
||||||
return _walk(nodes, state) }
|
return _walk(nodes, state) })
|
||||||
// walk...
|
// walk...
|
||||||
: _walk(nodes, state))
|
: _walk(nodes, state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* vim:set ts=4 sw=4 : */ return module })
|
* vim:set ts=4 sw=4 : */ return module })
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user