UNPKG

sangja

Version:

JavaScript data structures library

575 lines (525 loc) 15.1 kB
const Utils = require('./utils'); const Queue = require('./queue'); /** * @class * @memberof sangja */ class BinarySearchTree { /** * Creates a new BinarySearchTree. * Iterable a and option object b are optional. But if both are given, a must precede b. * @constructor * @param {iterable} [a=[]] - Iterator for initialize * @param {Object} [b={}] - Option object * @param {function} [b.key=utils.defaultKey] - Key function for each value. * Each value should be comparable with given key. * @param {function} [b.compare=utils.defaultCompare] - Compare function.<br> * If x precede y, compare(key(x), key(y)) < 0<br> * If y precede x, compare(key(x), key(y)) > 0<br> * If the order of x and y is the same, compare(key(x), key(y)) == 0 * @param {function} [b.reverse=false] - If true, compare result is inverted. */ constructor(a = {}, b = {}) { // null, null -> iterator: [], key,compare: default // iterator, null -> iterator: ok, key,compare: default // option, null -> iterator: [], key,compare: option // iterator, option -> iterator: ok, key,compare: option let iterator = null; let key = null; let compare = null; let reverse = null; if (Utils.isIterable(a)) { iterator = a; // If a is an iterable, b will be an option object.(If not given, {}) ({ key, compare, reverse } = b); } else { iterator = []; // If a is not an iterable, a will be an option object.(If not given, {}) ({ key, compare, reverse } = a); } this._root = null; this._options = { key: key || Utils.defaultKey, compare: compare || Utils.defaultCompare, reverse: reverse || false, }; this._key = this._options.key; if (this._options.reverse) { this._compare = (x, y) => this._options.compare(this._key(y), this._key(x)); } else { this._compare = (x, y) => this._options.compare(this._key(x), this._key(y)); } [...iterator].forEach(v => this.add(v)); } /** * Add value to the tree. * @param {*} value - The value to add to the tree. */ add(value) { if (this._root == null) { this._root = { left: null, right: null, children: 0, value, }; return; } let parent = null; let now = this._root; let direction = null; // 0 if left else 1; while (now) { parent = now; now.children += 1; if (this._compare(value, now.value) <= 0) { now = now.left; direction = 0; } else { now = now.right; direction = 1; } } now = { left: null, right: null, children: 0, value, }; if (direction === 0) { parent.left = now; } else { parent.right = now; } } /** * Add values to the the tree. * @param {iterable} iterable - The iterable object that contain values to add. */ addAll(iterable) { [...iterable].forEach(v => this.add(v)); } /** * Removes the lowest value of the tree and returns the value. * @returns {*} The lowest value of the tree. If empty, return undefined. */ pop() { if (this._root === null) { return undefined; } let parent = null; let now = this._root; while (now.left) { now.children -= 1; parent = now; now = now.left; } if (parent !== null) { parent.left = now.right; } else { this._root = now.right; } return now.value; } /** * Removes the given value in the tree and returns the value. * @returns {(*|undefined)} The lowest value of the tree. If empty or not found, return undefined. */ remove(v) { if (this._root === null || !this.includes(v)) { return undefined; } let parent = null; let now = this._root; while (now) { const comp = this._compare(this._key(v), this._key(now.value)); if (comp === 0) { break; } else if (comp < 0) { now.children -= 1; parent = now; now = now.left; } else { now.children -= 1; parent = now; now = now.right; } } // Not found if (now === null) { return undefined; } // If now has both child, move left rightmost child. if (now.left && now.right) { let rightmostParent = now; let rightmost = now.left; while (rightmost.right) { rightmostParent.children -= 1; rightmostParent = rightmost; rightmost = rightmost.right; } if (rightmostParent !== now) { // Found rightmost. rightmostParent.right = rightmost.left; } else { // Rightmost is now.left rightmostParent.left = rightmost.left; } const result = now.value; now.value = rightmost.value; return result; } // If now has left child, remove now. if (now.left) { if (now === this._root) { this._root = now.left; } else if (parent.left === now) { parent.left = now.left; } else { parent.right = now.left; } return now.value; } // If now has right child, remove now. if (now.right) { if (now === this._root) { this._root = now.right; } else if (parent.left === now) { parent.left = now.right; } else { parent.right = now.right; } return now.value; } // If now is left, remove now from parent. if (now === this._root) { this._root = null; } else if (parent.left === now) { parent.left = null; } else { parent.right = null; } return now.value; } /** * Removes a value that matches given predicate f and * returns value in the linked list. * @param {Function} f - Predicate * @returns {(*|undefined)} Found value. If not found, return undefined. * @throws {TypeError} When the given predicate is not a function. */ removeMatch(f) { if (typeof f !== 'function') { throw TypeError(); } if (this.isEmpty()) { return undefined; } function _getMatch(node) { if (f(node.value)) { return node.value; } return (node.left && _getMatch(node.left)) || (node.right && _getMatch(node.right)); } const removeVal = _getMatch(this._root); this.remove(removeVal); return removeVal; } /** * Returns the lowest value of the tree without changing the state of the tree. * @returns {*} The lowest value of the tree. If empty, return undefined. */ peek() { if (this._root === null) { return undefined; } let now = this._root; while (now) { if (now.left) { now = now.left; } } return now.value; } /** * Returns the value of the first element in the binary search tree * that satisfies the given predicate.<br> * Searches matching value by dfs order(preorder). * @param {function} f - Predicate * @returns {(*|undefined)} The the first value in the binary search tree * that satisfies the provided testing function. * When no element in the binary search tree satisfies * the provided testing function, return undefined. */ find(f) { const nodes = [this._root]; while (nodes.length > 0) { const now = nodes.pop(); if (now) { if (f(now.value)) { return now.value; } nodes.push(now.left); nodes.push(now.right); } } return undefined; } /** * Returns the number of elements in the tree. * @returns {number} The number of elements in the tree. */ size() { if (this._root === null) { return 0; } return this._root.children + 1; } /** * Returns whether the tree is empty. * @returns {boolean} True if the tree is empty. */ isEmpty() { return this._root === null; } /** * Removed all elements in the tree. */ clear() { this._root = null; } /** * Execute the given procedure f for each values. * @param {function} f - Procedure to execute */ forEach(f) { function _forEach(node) { if (node) { f(node.value); _forEach(node.left); _forEach(node.right); } } _forEach(this._root); } /** * Returns a new BinarySearchTree mapped with given function f. * @param {function} f - Function * @param {Object} [options=this._options] - Option object for initialize.<br> * If not given, inherits from this. * If only a portion is given, ingerits those that are not given. * @return {BinarySearchTree} BinarySearchTree([mapped values]) */ map(f, options) { const tree = new this.constructor(Utils.mergeOptions(options, this._options)); this.forEach(v => tree.add(f(v))); return tree; } /** * Returns a new BinarySearchTree whose values are mapped with given function f and flattened. * @param {function} f - Function (this.value) => iterable * @param {Object} [options=this._options] - Option object for initialize.<br> * If not given, inherits from this. * If only a portion is given, ingerits those that are not given. * @return {BinarySearchTree} BinarySearchTree([mapped and flattened values]) */ flatMap(f, options) { const tree = new this.constructor(Utils.mergeOptions(options, this._options)); this.forEach(v => tree.addAll([...f(v)])); return tree; } /** * Returns a new BinarySearchTree whose values are filtered with given predicate f. * @param {function} f - Predicate (this.value) => boolean * @param {Object} [options=this._options] - Option object for initialize.<br> * If not given, inherits from this. * If only a portion is given, ingerits those that are not given. * @return {BinarySearchTree} BinarySearchTree([filtered values]) */ filter(f, options) { const tree = new this.constructor(Utils.mergeOptions(options, this._options)); this.forEach((v) => { if (f(v)) { tree.add(v); } }); return tree; } /** * Return new tree with same items, but reversed option is inverted from this. * @return {BinarySearchTree} BinarySearchTree([reversed values]) */ reversed() { return new this.constructor(this, Utils.mergeOptions({ reverse: !this._options.reverse }, this._options)); } /** * If any of containing values satisfies given f, return true. * If none of values satisfy f or not contain any value, return false. * @param {function} f - Predicate * @returns {boolean} */ some(f) { function _some(node) { return f(node.value) || Boolean(node.left && _some(node.left)) || Boolean(node.right && _some(node.right)); } return Boolean(this._root) && _some(this._root); } /** * If all of containing values satisfies given f or not contain any value, return true. * If any of value doesn't satisfy f, return false. * @param {function} f - Predicate * @returns {boolean} */ every(f) { function _every(node) { return f(node.value) && Boolean(!node.left || _every(node.left)) && Boolean(!node.right || _every(node.right)); } return !this._root || _every(this._root); } /** * If contains given value v, return true. * @param {*} v * @returns {boolean} */ includes(v) { let now = this._root; while (now) { const comp = this._compare(this._key(v), this._key(now.value)); if (comp === 0) { return true; } if (comp < 0) { now = now.left; } else { now = now.right; } } return false; } /** * Returns whether the tree is empty. * @name Symbol.iterator * @generator * @property {generator} * @returns {boolean} True if the tree is empty. */ * [Symbol.iterator]() { yield* this.inorder(); } /** * Returns inorder iterator. * @param {Function} [f] - If f is given, execute f by inorder. * @returns {(generator|undefined)} Tree inorder iterator. If f if given, not return. */ // eslint-disable-next-line consistent-return inorder(f) { if (!f) { return (function* _inorderIterator(node) { if (node !== null) { yield* _inorderIterator(node.left); yield node.value; yield* _inorderIterator(node.right); } }(this._root)); } function _inorder(node) { if (node !== null) { _inorder(node.left); f(node.value); _inorder(node.right); } } _inorder(this._root); } /** * Returns preorder iterator. * @param {Function} [f] - If f is given, execute f by preorder. * @returns {(generator|undefined)} Tree preorder iterator. If f if given, not return. */ // eslint-disable-next-line consistent-return preorder(f) { if (!f) { return (function* _preorderIterator(node) { if (node !== null) { yield node.value; yield* _preorderIterator(node.left); yield* _preorderIterator(node.right); } }(this._root)); } function _preorder(node) { if (node !== null) { f(node.value); _preorder(node.left); _preorder(node.right); } } _preorder(this._root); } /** * Returns postorder iterator. * @param {Function} [f] - If f is given, execute f by postorder. * @returns {(generator|undefined)} Tree postorder iterator. If f if given, not return. */ // eslint-disable-next-line consistent-return postorder(f) { if (!f) { return (function* _postorderIterator(node) { if (node !== null) { yield* _postorderIterator(node.left); yield* _postorderIterator(node.right); yield node.value; } }(this._root)); } function _postorder(node) { if (node !== null) { _postorder(node.left); _postorder(node.right); f(node.value); } } _postorder(this._root); } /** * Returns breadth first iterator. * @param {Function} [f] - If f is given, execute f by breadth first. * @returns {(generator|undefined)} Tree breadth first iterator. If f if given, not return. */ // eslint-disable-next-line consistent-return breadthFirst(f) { if (!f) { return (function* _breadthFirstIterator(root) { const queue = new Queue(); queue.enqueue(root); while (queue.size() > 0) { const now = queue.dequeue(); if (now !== undefined && now !== null) { yield now.value; queue.enqueue(now.left); queue.enqueue(now.right); } } }(this._root)); } function _breadthFirst(root) { const queue = new Queue(); queue.enqueue(root); while (queue.size() > 0) { const now = queue.dequeue(); if (now !== undefined && now !== null) { f(now.value); queue.enqueue(now.left); queue.enqueue(now.right); } } } _breadthFirst(this._root); } } module.exports = BinarySearchTree;