UNPKG

atomic-fns

Version:

Like Lodash, but for ESNext and with types. Stop shipping code built for browsers from 2015.

339 lines (338 loc) 10.3 kB
import { compare, eq } from '../operators/index.js'; import { Mapping } from './abc.js'; /** * Base SplayTree node class. * @private */ class Node { key; value; left; right; constructor(key, value) { this.key = key; this.value = value; } get height() { if (this.left && this.right) return 1 + Math.max(this.right.height, this.left.height); if (this.right) return 1 + this.right.height; if (this.left) return 1 + this.left.height; return 0; } } // In-order traversal of a tree function* traverse(node) { while (node) { yield* traverse(node.left); yield node; node = node.right; } } /** * A splay tree is a *self-balancing binary* search tree with the additional property that recently accessed elements are quick to access again. * Insertion, look-up and removal in O(log n) *amortized* time. * For many patterns of non-random operations splay trees can take better than logarithmic time, without requiring advance knowledge of the pattern. * @see {@link https://en.wikipedia.org/wiki/Splay_tree Splay tree} * @see {@link https://web.archive.org/web/20220930173835/http://www14.in.tum.de/personen/albers/papers/ipl02.pdf Randomized Splay Trees} * @template K, V */ export class SplayTree extends Mapping { root; cmp; randSplay; count; /** * Create a new randomized Splay Tree * @param compareFn * @param p When an element is accessed, with probability `p` splay the tree. With probability `1 - p`, leave the tree as it is. */ constructor(compareFn, p = 0.5) { super(); this.cmp = compareFn ?? compare; this.count = 0; this.randSplay = p; } top() { return this.root?.key; } get size() { return this.count; } empty() { return !this.root; } get height() { if (!this.root) return 0; return this.root.height; } /** * Returns the value for `key` or `undefined` if the key is not found. * @param key The key to find. * @returns {?V} The value for the key or `undefined`. */ get(key) { if (!this.root) return; // Note: deterministic splaying (always moves key to front): // this.splay(key) // if (this.cmp(key, this.root.key) === 0) return this.root.value // Randomized Splay Trees https://doi.org/10.1016%2Fs0020-0190%2801%2900230-7 // The randomized idea is only moving accessed keys with a certain probability `p`. // Values of `p` closer to 1 tend to perform well. if (Math.random() <= this.randSplay) { this.splay(key); if (this.cmp(key, this.root.key) === 0) return this.root.value; else return undefined; } // Lookup the node down the tree let current = this.root; while (current) { const rank = this.cmp(key, current.key); if (rank === 0) return current.value; if (rank < 0) current = current.left; else current = current.right; } } /** * Inserts the `key` and `value` in the tree if the `key` does not exist. * @param {K} key A key which can be any comparable value * @param {V} value A value associated with the key * @returns */ set(key, value) { if (!this.root) { this.root = new Node(key, value); this.count += 1; return; } // Splay on the key to move the last node on the search path for // the key to the root of the tree. this.splay(key); const rank = this.cmp(key, this.root.key); if (rank === 0) { // Update the value this.root.value = value; return; } const node = new Node(key, value); if (rank > 0) { node.left = this.root; node.right = this.root.right; this.root.right = undefined; } else { node.right = this.root; node.left = this.root.left; this.root.left = undefined; } this.root = node; this.count += 1; } /** * Removes a specified key if it exists in the tree. The removed value is returned or `undefined` if not found. * @param {K} key The key to remove. * @returns {V} The removed value associated with `key`. */ remove(key) { if (!this.root) { return; } this.splay(key); if (!eq(key, this.root.key)) { return; } const removed = this.root; if (!this.root.left) { this.root = this.root.right; } else { const right = this.root.right; this.root = this.root.left; // // Splay to make sure that the new root has an empty right child. // this.splay(key); // // Insert the original right child as the right child of the new // root. // this.root.right = right; } this.count -= 1; return removed.value; } // // function find(key) // @param {number} key Key to find in the tree. // @return {SplayTree.Node} Node having the specified key. // // /** * Returns the minimum key in the tree or subtree. * @returns The minimum key. */ min(root = this.root) { if (!root) return; while (root.left) { root = root.left; } return root.key; } /** * Returns the maximum key in the tree or subtree. * @returns The maximum key. */ max(root = this.root) { if (!root) return; while (root.right) { root = root.right; } return root.key; } /** * Returns the largest key that is less than a given key. * @param key The given key * @returns The largest key found or `undefined`. */ lowerBound(key) { if (!this.root) return; // Splay on the key to move the node with the given key or the last // node on the search path to the top of the tree. this.splay(key); // Now the result is either the root node or the greatest node in // the left subtree. if (this.cmp(this.root.key, key) < 0) { return this.root; } else if (this.root.left) { return this.max(this.root.left); } } /** * This is the simplified top-down splaying method proposed by Sleator and Tarjan in {@link https://doi.org/10.1145%2F3828.3835 Self-Adjusting Binary Search Trees}. * @param key * @see {@link https://doi.org/10.1145%2F3828.3835 Self-Adjusting Binary Search Trees} */ splay(key) { if (!this.root) return; let stub, left, right; // eslint-disable-next-line stub = left = right = new Node(null, null); let root = this.root; while (true) { const rank = this.cmp(key, root.key); if (rank < 0) { if (!root.left) { break; } if (this.cmp(key, root.left.key) < 0) { // Rotate right. const tmp = root.left; root.left = tmp.right; tmp.right = root; root = tmp; if (!root.left) { break; } } // Link right. right.left = root; right = root; root = root.left; } else if (rank > 0) { if (!root.right) { break; } if (this.cmp(key, root.right.key) > 0) { // Rotate left. const tmp = root.right; root.right = tmp.left; tmp.left = root; root = tmp; if (!root.right) { break; } } // Link left. left.right = root; left = root; root = root.right; } else { break; } } // Assemble. left.right = root.left; right.left = root.right; root.left = stub.right; root.right = stub.left; this.root = root; } add(key) { return this.set(key, null); } /** * Returns `true` if the given key is in the tree. * @param {K} key The key to find. * @returns `true` if found. */ contains(key) { return this.get(key) !== undefined; } /** Alias of remove but just returns `true` instead of the deleted value. */ delete(key) { // since remove throws, this will throw too this.remove(key); return true; } clear() { if (!this.root) return; this.root.left = undefined; this.root.right = undefined; this.root = undefined; this.count = 0; } /** * Returns an ordered iterable of all the keys in the tree. * @returns {Iterable<K>} An iterable of keys in-order. */ *keys() { for (const node of traverse(this.root)) { yield node.key; } } /** * Returns an ordered iterable of all the keys and values in the tree. * @returns {Iterable<[K,V]>} A iterable of `[key, value]` pairs sorted by key. */ *entries() { for (const node of traverse(this.root)) { yield [node.key, node.value]; } } /** * Returns an ordered iterable of all the keys and values in the tree. * @returns {Iterable<V>} A iterable of `[key, value]` pairs sorted by key. */ *values() { for (const node of traverse(this.root)) { yield node.value; } } }