2018-09-28 13:59:05 +03:00
# walk.js
2018-09-28 15:36:27 +03:00
2018-09-28 13:59:05 +03:00
An extensible tree walk(..) framework...
2018-09-28 15:36:27 +03:00
2018-09-29 23:44:44 +03:00
2018-09-30 01:48:59 +03:00
- [walk.js ](#walkjs )
- [Theory and operation ](#theory-and-operation )
- [Constructing the walker and walking ( walk(..) ) ](#constructing-the-walker-and-walking--walk )
- [Getting and processing nodes ( getter(..) ) ](#getting-and-processing-nodes--getter )
- [Putting it all together ](#putting-it-all-together )
- [Installation and loading ](#installation-and-loading )
- [API ](#api )
2018-09-30 03:08:59 +03:00
- [`walk(..)` ](#walk )
2018-09-30 01:48:59 +03:00
- [`getter(..)` ](#getter )
2018-10-01 00:09:30 +03:00
- [`done(..)` ](#done )
2018-09-30 01:48:59 +03:00
- [Examples ](#examples )
2018-09-28 15:36:27 +03:00
## Theory and operation
2018-10-01 00:04:08 +03:00
This module generalizes structure traverse (*walking*, recursion). This is done via a `walk(..)` function that recieves a user-defined `getter(..)` function and returns a *walker* .
2018-09-29 23:44:44 +03:00
2018-09-30 01:48:59 +03:00
### Constructing the walker and walking ( walk(..) )
2018-09-28 15:36:27 +03:00
2018-09-30 01:48:59 +03:00
`walk(getter[, done])(state, ...nodes) -> state`
`walk(getter[, done], state)(...nodes) -> state`
`walk(getter[, done], state, ...nodes) -> state`
2018-09-30 03:54:40 +03:00
- Recieves a `getter(..)` function, an optional `done(..)` function, a `state` and a list of `nodes` ,
2018-09-30 01:48:59 +03:00
- Iterates through `nodes` , calling the `getter(..)` per node and threading the `state` through each call,
2018-09-30 03:54:40 +03:00
- If `done(..)` is given, it is called after *walking* is done, threading `state` through,
- Returns the `state` when walking is done.
2018-09-28 15:36:27 +03:00
2018-09-29 23:44:44 +03:00
2018-09-30 01:48:59 +03:00
### Getting and processing nodes ( getter(..) )
2018-09-29 23:44:44 +03:00
2018-09-30 00:16:09 +03:00
`getter(state, node, next, stop) -> state`
2018-09-30 03:54:40 +03:00
2018-09-30 00:16:09 +03:00
- Recieves `state` , `node` and two control functions: `next` and `stop` ,
2018-09-30 23:48:47 +03:00
- Called in a context (`this` ), persistent within one `walk(..)` call, inherited from *walker's* `.prototype` . This context is usable to store data between `getter(..)` calls,
2018-09-28 15:36:27 +03:00
- Can process `node` and `state` ,
2018-09-30 03:54:40 +03:00
- Can queue nodes for walking via `next('queue', state, ...nodes) -> state` ,
- Can walk nodes directly via `next('do', state, ...nodes) -> state` ,
2018-09-30 23:48:47 +03:00
- Can abort *walking* and return a state via `stop()` (returning `undefined` ) or `stop(state)` ,
2018-09-30 03:54:40 +03:00
- Returns `state` .
2018-09-30 23:48:47 +03:00
`state` is *threaded* through all the `getter(..)` and `done(..)` calls, i.e. the first call gets the input `state` , each next call gets the previous call's returned `state` passed in, then its returned `state` gets passed on, and so on. The last function's returned `state` is in turn returned from the *walker* .
2018-09-30 03:54:40 +03:00
2018-09-30 23:48:47 +03:00
Within a single *walker* call, all the `getter(..)` and `done(..)` calles a run in one common context. This context can be used to store (*thread*)additional temporary data through the *walker* . This context is dropped as soon as the *walker* returns. This context object is inherited from *walker's* `.prototype` enabling the user to define persistent methods and static data usable from within the *walker's* `getter(..)` and `done(..)` .
2018-09-28 15:36:27 +03:00
2018-09-29 23:44:44 +03:00
### Putting it all together
2018-09-28 15:36:27 +03:00
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
```
2018-09-29 23:44:44 +03:00
And for *flat* lists `.reduce(..)` and friends are simpler and more logical. `walk(..)` is designed to simplify more complex cases:
2018-09-28 15:36:27 +03:00
- The input is not *flat* :
```javascript
2018-09-29 23:44:44 +03:00
// sum the items in a *deep* array (depth-first)...
2018-09-30 00:16:09 +03:00
var sum = walk(function(r, n, next){
2018-09-28 15:36:27 +03:00
return n instanceof Array ?
2018-09-30 01:59:57 +03:00
next('do', r, ...n)
2018-09-28 15:36:27 +03:00
: r + n }, 0)
sum( [1, [2, 3], 4, [[5], 6]] ) // -> 21
```
2018-09-30 00:16:09 +03:00
For reference here is a *recursive* `.reduce(..)` example, already a bit more complex:
2018-09-28 15:47:58 +03:00
```javascript
function sumr(l){
return l.reduce(function(r, e){
return r + (e instanceof Array ?
sumr(e)
: e) }, 0) }
sumr( [1, [2, 3], 4, [[5], 6]] ) // -> 21
```
2018-09-29 23:44:44 +03:00
2018-09-28 15:36:27 +03:00
- Need to abort the recursion prematurelly:
```javascript
2018-09-28 15:47:58 +03:00
// check if structure contains 0...
2018-09-30 00:16:09 +03:00
var containsZero = walk(function(r, e, next, stop){
2018-09-29 03:13:46 +03:00
// NOTE: we'll only count leaf nodes...
this.nodes_visited = (this.nodes_visited || 0)
2018-09-28 15:36:27 +03:00
return e === 0 ?
2018-09-30 00:16:09 +03:00
// abort search, report number of nodes visited...
2018-09-29 03:13:46 +03:00
stop(this.nodes_visited+1)
2018-09-28 15:36:27 +03:00
: e instanceof Array ?
2018-09-30 02:01:03 +03:00
next('do', ...e)
2018-09-29 03:13:46 +03:00
: (this.nodes_visited++, r) }, false)
2018-09-28 15:36:27 +03:00
2018-09-29 03:13:46 +03:00
containsZero( [1, [2, 0], 4, [[5], 6]] ) // -> 3
2018-09-28 15:36:27 +03:00
containsZero( [1, [2, 5], 4, [[5], 6]] ) // -> false
```
2018-09-28 15:47:58 +03:00
See a more usefull search in [examples ](#examples )...
2018-09-28 15:36:27 +03:00
2018-09-30 01:48:59 +03:00
2018-09-28 15:36:27 +03:00
## Installation and loading
```shell
$ npm install --save generic-walk
```
```javascript
var walk = require('generic-walk').walk
```
2018-09-30 02:03:41 +03:00
*Note: This module supports both [`requirejs(..)` ](https://requirejs.org ) and node's `require(..)` **
2018-09-28 15:36:27 +03:00
2018-09-30 01:48:59 +03:00
2018-09-28 15:36:27 +03:00
## API
2018-09-30 03:08:59 +03:00
### `walk(..)`
2018-09-28 15:36:27 +03:00
`walk(getter(..)) -> walker(state, ...nodes)`
2018-09-30 01:48:59 +03:00
`walk(getter(..), done(..)) -> walker(state, ...nodes)`
2018-09-28 15:36:27 +03:00
Construct a reusable walker.
`walk(getter(..), state) -> walker(...nodes)`
2018-09-30 01:48:59 +03:00
`walk(getter(..), done(..), state) -> walker(...nodes)`
2018-09-28 15:36:27 +03:00
Construct a reusable walker with fixed initial state.
`walk(getter(..), state, ...nodes) -> result`
2018-09-30 01:48:59 +03:00
`walk(getter(..), done(..), state, ...nodes) -> result`
2018-09-28 15:36:27 +03:00
Walk the nodes.
2018-09-30 03:08:59 +03:00
*Note that `state` can not be a function unless `done(..)` is provided.*
2018-09-30 01:48:59 +03:00
2018-09-28 15:36:27 +03:00
2018-09-30 01:48:59 +03:00
### `getter(..)`
2018-09-28 15:36:27 +03:00
2018-09-30 00:16:09 +03:00
`getter(state, node, next(..), stop(..)) -> state`
2018-09-30 03:08:59 +03:00
User provided function, called to process a node. `getter(..)` is passed the current `state` , the `node` and two control functions: `next(..)` and `stop(..)` to control the *walk* execution flow.
2018-09-28 15:36:27 +03:00
2018-09-30 03:54:40 +03:00
*`next('queue', state, ...nodes) -> state` *
2018-09-30 03:08:59 +03:00
Queue `nodes` for walking (*breadth-first*). The queued nodes will get *walked* after this level of nodes is done, i.e. after the `getter(..)` is called for each node on current level.
*Note that this does not change the state in any way and returns it as-is, this is done for signature compatibility with `next('do', ..)` *
2018-09-28 15:36:27 +03:00
2018-09-30 03:54:40 +03:00
*`next('do', state, ...nodes) -> state` *
2018-09-30 03:08:59 +03:00
Walk `nodes` (*depth-first*) and return `state` . The nodes will get *walked* immidiately.
2018-09-28 15:36:27 +03:00
2018-09-30 03:54:40 +03:00
*`stop()` *
*`stop(state)` *
2018-09-28 15:36:27 +03:00
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.*
2018-10-01 00:09:30 +03:00
### `done(..)`
2018-09-30 01:48:59 +03:00
`done(state) -> state`
User provided function, if given, is called by the *walker* after walking is done (no more nodes to handle). `state` is passed as argument and the return value is returned from the *walker* . This is run in the same context (`this` ) as `getter(..)` .
2018-09-28 15:36:27 +03:00
## Examples
Sum all the values of a nested array (breadth-first)...
```javascript
var sum = walk(function(res, node, next){
return node instanceof Array ?
2018-09-30 00:16:09 +03:00
next('queue', res, ...node)
2018-09-28 15:36:27 +03:00
: 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
2018-09-30 00:16:09 +03:00
var sumd = walk(function(res, node, next){
2018-09-28 15:36:27 +03:00
return node instanceof Array ?
2018-09-30 00:16:09 +03:00
// yes, this is the only difference...
next('do', res, ...node)
2018-09-28 15:36:27 +03:00
: res + node }, 0)
sumd([1, [2], 3, [[4, 5]]]) // -> 15 ...walks the nodes: 1, 2, 3, 4, 5
```
2018-09-29 23:44:44 +03:00
To explicitly see the paths the `sum` /`sumd` take we need to modify them a little:
2018-09-28 15:36:27 +03:00
```javascript
2018-09-30 00:16:09 +03:00
var makeSummer = function(mode){
2018-09-30 01:48:59 +03:00
var walker = walk(
function(res, node, next){
this.log(node)
return node instanceof Array ?
next(mode == 'breadth-first' ? 'queue' : 'do',
res,
...node)
: res + node },
// print the path when done...
function(state){
console.log('-->', this.path)
return state
},
1)
// log the nodes...
walker.prototype.log = function(node){
this.path = node instanceof Array ?
this.path
: (this.path || []).concat([node]) }
return walker
}
2018-09-30 00:16:09 +03:00
var sum = makeSummer('breadth-first')
var sumd = makeSummer('depth-first')
2018-09-28 15:36:27 +03:00
2018-09-29 03:13:46 +03:00
sum([1, [2], 3, [[4, 5]]]) // -> 15
2018-09-28 15:36:27 +03:00
2018-09-29 03:13:46 +03:00
sumd([1, [2], 3, [[4, 5]]]) // -> 15
```
2018-09-28 15:36:27 +03:00
FInd first zero in tree and return it's path...
```javascript
2018-09-29 03:13:46 +03:00
// 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){
2018-09-30 00:16:09 +03:00
return walk(function(res, node, next, stop){
2018-09-29 03:13:46 +03:00
// 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),
2018-09-30 00:16:09 +03:00
next('do', res, ...Object.entries(v)))
2018-09-29 03:13:46 +03:00
: res}, false, L) }
2018-09-28 15:36:27 +03:00
firstZero([10, 5, [{x: 1, y: 0}, 4]]) // -> ['2', '0', 'y']
```