sangja
Version:
JavaScript data structures library
575 lines (525 loc) • 15.1 kB
JavaScript
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;