UNPKG

digital-tree

Version:
286 lines (234 loc) 6.7 kB
const { isString, isFunction } = require('util') const debug = require('./lib/debug')('Trie') const _DFS = Symbol('dfs') const _BFS = Symbol('bfs') const HashMapNode = require('./lib/HashMapNode') const TrieIterator = require('./lib/TrieIterator') const NoopIterator = require('./lib/NoopIterator') const noopIterator = new NoopIterator() class Trie { static get DFS() { return _DFS } static get BFS() { return _BFS } /** * Create a new Trie * * @param {variant} [options.rootValue] The root value of the trie, normally you will not use this. * @param {Symbol} [options.iterationOrder=Trie.DFS] The trie is `iterable`, setting this will change the default iteration order. * iteration order can be either `Trie.BFS` or `Trie.DFS`. You can still use an explicit iteration order by calling `trie.bfsIterator()` or `trie.dfsIterator()` * @param {HashMapNode} [options.NodeClass=HashMapNode] * * @return {Trie} */ static create({ iterationOrder = _DFS, NodeClass = HashMapNode } = {}) { if (iterationOrder !== _DFS && iterationOrder !== _BFS) { throw new Error('invalid iteration order, try Trie.BFS or Trie.DFS') } return new Trie({ NodeClass, iterationOrder }) } constructor({ NodeClass, iterationOrder }) { this._nodeClass = NodeClass this._root = this._newNode() this._iterationOrder = iterationOrder } /** * put something in the tree. * * @param {Iterable} key - each member in the array is a level in the tree * @param {variant} [value=true] - the value to store * */ put(key, value = true) { debug('put( {%s}, {%s} )', key, value) if (!this._isValidKey(key)) { throw new Error('invalid key') } let current = this._root let count = 0 for (let part of key) { let node = current.getChild(part) if (node === undefined) { node = this._newNode() current.addChild(part, node) } count++ current = node } // prevent "losing" a value in root // if iterable was "empty" // empty interables doesn't make sense anyways if (count === 0) { throw new Error('invalid key') } current.value = value } putAll(iterable) { for (let key of iterable) { this.put(key, true) } } /** * get something from the tree. * * @param {Iterable} [key] - a path in the tree. * * @return {variant} the value that was placed under that key */ get(key) { debug('get( {%s} )', key) const current = this._getNode(key) if (current === undefined) return return current.value } /** * get a Trie view of the tree under "key" * * @param {Iterable} key * @param {boolean} [shallow] defaults to false * * @return {Trie} */ getSubTrie(key, shallow = false) { debug('getSubTrie( {%s} }', key) const current = this._getNode(key) return this._newTrieLikeThis(current, shallow) } /** * clone this trie. * * - Object keys will not be cloned * * @return {Trie} */ clone() { return this._newTrieLikeThis(this._root) } /** * remove something from the tree * * @param {Iterable} key * * @return {Trie} subtree that was removed */ remove(key) { // this whole thing can go away if HashMapNode will have a // parent reference... then removeChild() will not have // to accept keyPart const { current, parent, keyPart } = this._getNodeAndParent(key) if (current === undefined) return parent.removeChild(keyPart) return this._newTrieLikeThis(current) } /** * search for all values that are associated with keys that have the specified prefix * values will be ordered based on the default ordering of the trie (dfs/bfs) * * @param {Iterable} prefix * @param {boolean} [options.includeKeys=false] if set to true result will include keys as values. * @return {Iterable} */ search(prefix, { includeKeys = false } = {}) { if (!this._isValidKey(prefix)) { throw new Error('invalid key') } const node = this._getNode(prefix) if (node === undefined) { return noopIterator } return this._newTrieIterator(node, { includeKeys, iterationOrder: this._iterationOrder, prefix }) } [Symbol.iterator]() { return this._newTrieIterator(this._root) } /** * return a DFS iterator for this trie * * @param {boolean} [includeKeys=false] if set to true result will include keys as values. * @return {Iterator} */ dfsIterator(includeKeys = false) { return this._newTrieIterator(this._root, { includeKeys, iterationOrder: Trie.DFS }) } /** * return a BFS iterator for this trie * * @param {boolean} [includeKeys=false] if set to true result will include keys as values. * @return {Iterator} */ bfsIterator(includeKeys = false) { return this._newTrieIterator(this._root, { includeKeys, iterationOrder: Trie.BFS }) } _getNode(key) { if (!this._isValidKey(key)) { throw new Error('invalid key') } let current = this._root let count = 0 for (let part of key) { let node = current.getChild(part) if (node === undefined) { return } count++ current = node } // prevent obtaining access to root // if iterable is empty if (count === 0) { throw new Error('invalid key') } return current } _getNodeAndParent(key) { if (!this._isValidKey(key)) { throw new Error('invalid key') } let keyPart = undefined let parent = undefined let current = this._root let count = 0 for (keyPart of key) { let node = current.getChild(keyPart) if (node === undefined) { return {} } count++ parent = current current = node } // prevent getting access to root // if iterable is empty if (count > 0) { return { current, parent, keyPart } } throw new Error('invalid key') } _newNode(value) { return new this._nodeClass(value) } _isValidKey(key) { // const rightType = Array.isArray(key) || isString(key) // return rightType && key.length > 0 return isFunction(key[Symbol.iterator]) } _newTrieLikeThis(root, shallow = false) { const trie = new Trie({ NodeClass: this._nodeClass, iterationOrder: this._iterationOrder }) trie._root = shallow ? root : root.clone() return trie } _newTrieIterator(rootNode, { includeKeys = false, iterationOrder = this._iterationOrder, prefix } = {}) { if (iterationOrder === _DFS) { return new TrieIterator(rootNode, { memory: TrieIterator.Stack, includeKeys, prefix }) } if (iterationOrder === _BFS) { return new TrieIterator(rootNode, { memory: TrieIterator.Queue, includeKeys, prefix }) } throw new Error('invalid iteration order, try Trie.BFS or Trie.DFS') } } module.exports = Trie