/** This module is browser compatible. */ import { ascend, BSTree } from "./bs_tree.ts"; import { direction, RBNode } from "./rb_node.ts"; export * from "./_comparators.ts"; /** * A red-black tree. This is a kind of self-balancing binary search tree. * The values are in ascending order by default, * using JavaScript's built in comparison operators to sort the values. */ export class RBTree extends BSTree { declare protected root: RBNode | null; constructor( compare: (a: T, b: T) => number = ascend, ) { super(compare); } /** Creates a new red-black tree from an array like or iterable object. */ static override from( collection: ArrayLike | Iterable | RBTree, ): RBTree; static override from( collection: ArrayLike | Iterable | RBTree, options: { Node?: typeof RBNode; compare?: (a: T, b: T) => number; }, ): RBTree; static override from( collection: ArrayLike | Iterable | RBTree, options: { compare?: (a: U, b: U) => number; map: (value: T, index: number) => U; thisArg?: V; }, ): RBTree; static override from( collection: ArrayLike | Iterable | RBTree, options?: { compare?: (a: U, b: U) => number; map?: (value: T, index: number) => U; thisArg?: V; }, ): RBTree { let result: RBTree; let unmappedValues: ArrayLike | Iterable = []; if (collection instanceof RBTree) { result = new RBTree( options?.compare ?? (collection as unknown as RBTree).compare, ); if (options?.compare || options?.map) { unmappedValues = collection; } else { const nodes: RBNode[] = []; if (collection.root) { result.root = RBNode.from(collection.root as unknown as RBNode); nodes.push(result.root); } while (nodes.length) { const node: RBNode = nodes.pop()!; const left: RBNode | null = node.left ? RBNode.from(node.left) : null; const right: RBNode | null = node.right ? RBNode.from(node.right) : null; if (left) { left.parent = node; nodes.push(left); } if (right) { right.parent = node; nodes.push(right); } } } } else { result = (options?.compare ? new RBTree(options.compare) : new RBTree()) as RBTree; unmappedValues = collection; } const values: Iterable = options?.map ? Array.from(unmappedValues, options.map, options.thisArg) : unmappedValues as U[]; for (const value of values) result.insert(value); return result; } protected removeFixup(parent: RBNode | null, current: RBNode | null) { while (parent && !current?.red) { const direction: direction = parent.left === current ? "left" : "right"; const siblingDirection: direction = direction === "right" ? "left" : "right"; let sibling: RBNode | null = parent[siblingDirection]; if (sibling?.red) { sibling.red = false; parent.red = true; this.rotateNode(parent, direction); sibling = parent[siblingDirection]; } if (sibling) { if (!sibling.left?.red && !sibling.right?.red) { sibling!.red = true; current = parent; parent = current.parent; } else { if (!sibling[siblingDirection]?.red) { sibling[direction]!.red = false; sibling.red = true; this.rotateNode(sibling, siblingDirection); sibling = parent[siblingDirection!]; } sibling!.red = parent.red; parent.red = false; sibling![siblingDirection]!.red = false; this.rotateNode(parent, direction); current = this.root; parent = null; } } } if (current) current.red = false; } /** * Adds the value to the binary search tree if it does not already exist in it. * Returns true if successful. */ override insert(value: T): boolean { let node = this.insertNode(RBNode, value) as (RBNode | null); if (node) { while (node.parent?.red) { let parent: RBNode = node.parent!; const parentDirection: direction = parent.directionFromParent()!; const uncleDirection: direction = parentDirection === "right" ? "left" : "right"; const uncle: RBNode | null = parent.parent![uncleDirection] ?? null; if (uncle?.red) { parent.red = false; uncle.red = false; parent.parent!.red = true; node = parent.parent!; } else { if (node === parent[uncleDirection]) { node = parent; this.rotateNode(node, parentDirection); parent = node.parent!; } parent.red = false; parent.parent!.red = true; this.rotateNode(parent.parent!, uncleDirection); } } this.root!.red = false; } return !!node; } /** * Removes node value from the binary search tree if found. * Returns true if found and removed. */ override remove(value: T): boolean { const node = this.removeNode(value) as (RBNode | null); if (node && !node.red) { this.removeFixup(node.parent, node.left ?? node.right); } return !!node; } }