Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ngryman/tree-crawl

:leaves: Agnostic tree traversal library.
https://github.com/ngryman/tree-crawl

bfs dfs traversal tree visitor

Last synced: about 2 months ago
JSON representation

:leaves: Agnostic tree traversal library.

Awesome Lists containing this project

README

        

# tree-crawl [![travis][travis-image]][travis-url] [![codecov][codecov-image]][codecov-url] [![greenkeeper][greenkeeper-image]][greenkeeper-url] [![size][size-image]][size-url]

> Agnostic tree traversal library.

[travis-image]: https://img.shields.io/travis/ngryman/tree-crawl.svg?style=flat
[travis-url]: https://travis-ci.org/ngryman/tree-crawl
[codecov-image]: https://img.shields.io/codecov/c/github/ngryman/tree-crawl.svg
[codecov-url]: https://codecov.io/github/ngryman/tree-crawl
[greenkeeper-image]: https://badges.greenkeeper.io/ngryman/tree-crawl.svg
[greenkeeper-url]: https://greenkeeper.io/
[size-image]: http://img.badgesize.io/https://unpkg.com/[email protected]/dist/tree-crawl.min.js?compression=gzip
[size-url]: https://unpkg.com/[email protected]/dist/tree-crawl.min.js

- **Agnostic**: Supports any kind of tree. You provide a way to access a node's children, that's it.
- **Fast**: Crafted to be optimizer-friendly. See [performance](#performance) for more details.
- **Mutation friendly**: Does not 💥 when you mutate the tree.
- **Multiple orders**: Supports DFS pre and post order and BFS traversals.

## Quickstart

### Installation

You can install `tree-crawl` with `yarn`:

```sh
$ yarn add tree-crawl
```

Alternatively using `npm`:

```sh
$ npm install --save tree-crawl
```

### Usage

```js
import crawl from 'tree-crawl'

// traverse the tree in pre-order
crawl(tree, console.log)
crawl(tree, console.log, { order: 'pre' })

// traverse the tree in post-order
crawl(tree, console.log, { order: 'post' })

// traverse the tree using `childNodes` as the children key
crawl(tree, console.log, { getChildren: node => node.childNodes })

// skip a node and its children
crawl(tree, (node, context) => {
if ('foo' === node.type) {
context.skip()
}
})

// stop the walk
crawl(tree, (node, context) => {
if ('foo' === node.type) {
context.break()
}
})

// remove a node
crawl(tree, (node, context) => {
if ('foo' === node.type) {
context.parent.children.splice(context.index, 1)
context.remove()
}
})

// replace a node
crawl(tree, (node, context) => {
if ('foo' === node.type) {
const node = {
type: 'new node',
children: [
{ type: 'new leaf' }
]
}
context.parent.children[context.index] = node
context.replace(node)
}
})
```

## FAQ

### How can I get the path of the current node ([#37](https://github.com/ngryman/tree-crawl/issues/37))?

**tl;dr It's easy for DFS, less easy for BFS**

If you are using DFS you can use the following utility function:
```javascript
const getPath = context =>
context.cursor.stack.xs.reduce((path, item) => {
if (item.node) {
path.push(item.node)
}
return path
}, [])
```
If you are really concerned about performance, you could read items from the stack directly. Each item has a `node` and `index` property that you can use. The first item in the stack can be discarded and will have a `node` set to `null`. Be aware that you should not mutate the stack, or it will break the traversal.

If you are using BFS, things gets more complex. A *simple hacky* way to do so is to traverse the tree using DFS first. You can ad a `path` property to your nodes using the method above. And then do your regular BFS traversal using that `path` property.

## API

### Iteratee

- **See: [Traversal context](#traversal-context).**

Called on each node of the tree.

Type: [Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)

**Parameters**

- `node` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Node being visited.
- `context` **[Context](#context)** Traversal context

### Options

Walk options.

Type: [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)

**Parameters**

- `node`

**Properties**

- `getChildren` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?** Return a node's children.
- `order` **(`"pre"` \| `"post"` \| `"bfs"`)?** Order of the walk either in DFS pre or post order, or
BFS.

**Examples**

_Traverse a DOM tree._

```javascript
crawl(document.body, doSomeStuff, { getChildren: node => node.childNodes })
```

_BFS traversal_

```javascript
crawl(root, doSomeStuff, { order: 'bfs' })
```

### crawl

Walk a tree recursively.

By default `getChildren` will return the `children` property of a node.

**Parameters**

- `root` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Root node of the tree to be walked.
- `iteratee` **[Iteratee](#iteratee)** Function invoked on each node.
- `options` **[Options](#options)?** Options customizing the walk.

### Context

A traversal context.

Four operations are available. Note that depending on the traversal order, some operations have
no effects.

**Parameters**

- `flags` **Flags**
- `cursor` **Cursor**

#### skip

Skip current node, children won't be visited.

**Examples**

```javascript
crawl(root, (node, context) => {
if ('foo' === node.type) {
context.skip()
}
})
```

#### break

Stop traversal now.

**Examples**

```javascript
crawl(root, (node, context) => {
if ('foo' === node.type) {
context.break()
}
})
```

#### remove

Notifies that the current node has been removed, children won't be visited.

Because `tree-crawl` has no idea about the intrinsic structure of your tree, you have to
remove the node yourself. `Context#remove` only notifies the traversal code that the structure
of the tree has changed.

**Examples**

```javascript
crawl(root, (node, context) => {
if ('foo' === node.type) {
context.parent.children.splice(context.index, 1)
context.remove()
}
})
```

#### replace

Notifies that the current node has been replaced, the new node's children will be visited
instead.

Because `tree-crawl` has no idea about the intrinsic structure of your tree, you have to
replace the node yourself. `Context#replace` notifies the traversal code that the structure of
the tree has changed.

**Parameters**

- `node` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Replacement node.

**Examples**

```javascript
crawl(root, (node, context) => {
if ('foo' === node.type) {
const node = {
type: 'new node',
children: [
{ type: 'new leaf' }
]
}
context.parent.children[context.index] = node
context.replace(node)
}
})
```

#### parent

Get the parent of the current node.

Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** Parent node.

#### depth

Get the **depth** of the current node. The depth is the number of ancestors the current node
has.

Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Depth.

#### level

Get the **level** of current node. The level is the number of ancestors+1 the current node has.

Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Level.

#### index

Get the index of the current node.

Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Node's index.

## Performance

`tree-crawl` is built to be super fast and traverse potentially huge trees. It's possible because it implements its own stack and queue for traversal algorithms and makes sure the code is optimizable by the VM.

If you do need real good performance please consider reading this [checklist] first.

Your main objective is to keep the traversal code optimized and avoid de-optimizations and bailouts. To do so, your nodes should have the same [hidden class] and your code stay [monomorphic].

[checklist]: http://mrale.ph/blog/2011/12/18/v8-optimization-checklist.html

[hidden class]: http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html

[monomorphic]: http://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html

## Related

- [arbre](https://github.com/arbrejs/arbre) Agnostic tree library.
- [tree-mutate](https://github.com/ngryman/tree-mutate) Agnostic tree mutation library.
- [tree-morph](https://github.com/ngryman/tree-morph) Agnostic tree morphing library.

## License

MIT © [Nicolas Gryman](http://ngryman.sh)