walk.js
An extensible tree walk(..) framework...
Theory and operation
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.
Constructing the walker and walking ( walk(..) )
walk(getter[, done])(state, ...nodes) -> state
walk(getter[, done], state)(...nodes) -> state
walk(getter[, done], state, ...nodes) -> state
- Recieves a
getter(..)
function, an optionaldone(..)
function, astate
and a list ofnodes
, - Iterates through
nodes
, calling thegetter(..)
per node and threading thestate
through each call, - If
done(..)
is given, it is called after walking is done, threadingstate
through, - Returns the
state
when walking is done.
Getting and processing nodes ( getter(..) )
getter(state, node, next, stop) -> state
- Recieves
state
,node
and two control functions:next
andstop
, - Called in a context (
this
), persistent within onewalk(..)
call, inherited from walker's.prototype
. This context is usable to store data betweengetter(..)
calls, - Can process
node
andstate
, - Can queue nodes for walking via
next('queue', state, ...nodes) -> state
, - Can walk nodes directly via
next('do', state, ...nodes) -> state
, - Can abort walking and return a state via
stop()
(returningundefined
) orstop(state)
, - Returns
state
.
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.
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(..)
.
Putting it all together
A trivial flat example...
// -> 6
The above is essentially equivalent to...
123 // -> 6
And for flat lists .reduce(..)
and friends are simpler and more logical. walk(..)
is designed to simplify more complex cases:
-
The input is not flat:
// sum the items in a *deep* array (depth-first)...var sum =// -> 21For reference here is a recursive
.reduce(..)
example, already a bit more complex:{return l}// -> 21 -
Need to abort the recursion prematurelly:
// check if structure contains 0...var containsZero =// -> 3// -> falseSee a more usefull search in examples...
Installation and loading
$ npm install --save generic-walk
var walk = walk
Note: This module supports both requirejs(..)
and node's require(..)
*
API
walk(..)
walk(getter(..)) -> walker(state, ...nodes)
walk(getter(..), done(..)) -> walker(state, ...nodes)
Construct a reusable walker.
walk(getter(..), state) -> walker(...nodes)
walk(getter(..), done(..), state) -> walker(...nodes)
Construct a reusable walker with fixed initial state.
walk(getter(..), state, ...nodes) -> result
walk(getter(..), done(..), state, ...nodes) -> result
Walk the nodes.
Note that state
can not be a function unless done(..)
is provided.
getter(..)
getter(state, node, next(..), stop(..)) -> state
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.
next('queue', state, ...nodes) -> state
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', ..)
.
Note that a common instinct here is to expect to get a promise as a result of next('queue', ..)
, this is not needed as the getter(..)
will eventually get all the queued nodes as input anyway and adding promises into the mix would only complicate things.
next('do', state, ...nodes) -> state
Walk nodes
(depth-first) 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.
done(..)
done(state, mode) -> 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. If walking was stopped via stop(..)
mode will be 'stopped'
otherwise it is 'done'
. This is run in the same context (this
) as getter(..)
.
Examples
Sum all the values of a nested array (breadth-first)...
var sum = // -> 15 ...walks the nodes: 1, 3, 2, 4, 5
Sum all the values of a nested array (depth-first)...
var sumd = // -> 15 ...walks the nodes: 1, 2, 3, 4, 5
To explicitly see the paths the sum
/sumd
take we need to modify them a little:
var { var walker = // log the nodes... walkerprototype{ thispath = node instanceof Array ? thispath : thispath || } return walker} var sum = var sumd = // -> 15 // -> 15
FInd first zero in tree and return it's path...
// 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 { return } // -> ['2', '0', 'y']
Contacts, feedback and contributions
License
Copyright (c) 2018, Alex A. Naanou, All rights reserved.