import { Compare, Relation, assertNever, freeze, nat } from "../index.js"; export type RedBlackTree = Red | Black | null; export class Red { elem : E left : RedBlackTree right : RedBlackTree constructor(elem : E, left : RedBlackTree, right : RedBlackTree) { this.elem = elem; this.left = left; this.right = right; freeze(this); } } freeze(Red); export class Black { elem : E left : RedBlackTree right : RedBlackTree constructor(elem : E, left : RedBlackTree, right : RedBlackTree) { this.elem = elem; this.left = left; this.right = right; freeze(this); } } freeze(Black); export function isRed(tree : RedBlackTree) : tree is Red { return tree instanceof Red; } function isBlack(tree : RedBlackTree) : tree is Black { return tree instanceof Black; } export function isEmpty(tree : RedBlackTree) : tree is null { return tree === null; } export function empty() : RedBlackTree { return null; } export function isElementOf(order : Compare, x : E, tree : RedBlackTree) : boolean { function member(tree : RedBlackTree) : boolean { if (isEmpty(tree)) return false; const c = order.compare(x, tree.elem); switch(c) { case Relation.UNRELATED: throw new Error("RedBlackTree: Cannot compare '" + x + "' with '" + tree.elem + "'."); case Relation.EQUAL: return true; case Relation.LESS: return member(tree.left); case Relation.GREATER: return member(tree.right); default: assertNever(c); } } return member(tree); } export function findEqualElement(order : Compare, x : E, tree : RedBlackTree) : E | undefined { function find(tree : RedBlackTree) : E | undefined { if (isEmpty(tree)) return undefined; const c = order.compare(x, tree.elem); switch(c) { case Relation.UNRELATED: throw new Error("RedBlackTree: Cannot compare '" + x + "' with '" + tree.elem + "'."); case Relation.EQUAL: return tree.elem; case Relation.LESS: return find(tree.left); case Relation.GREATER: return find(tree.right); default: assertNever(c); } } return find(tree); } export function findMinimumElement(tree : RedBlackTree) : E | undefined { function find(tree : RedBlackTree) : E | undefined { if (isEmpty(tree)) return undefined; return find(tree.left); } return find(tree); } export function findMaximumElement(tree : RedBlackTree) : E | undefined { function find(tree : RedBlackTree) : E | undefined { if (isEmpty(tree)) return undefined; return find(tree.right); } return find(tree); } function mkRed(left : RedBlackTree, elem : E, right : RedBlackTree) : RedBlackTree { return new Red(elem, left, right); } function mkBlack(left : RedBlackTree, elem : E, right : RedBlackTree) : RedBlackTree { return new Black(elem, left, right); } function forceBlack(tree : RedBlackTree) : RedBlackTree { if (isRed(tree)) return mkBlack(tree.left, tree.elem, tree.right); else return tree; } function splitRed(tree : Red) : [RedBlackTree, E, RedBlackTree] { if (!isRed(tree)) throw new Error("splitRed"); return [tree.left, tree.elem, tree.right]; } function splitBlack(tree : Black) : [RedBlackTree, E, RedBlackTree] { if (!isBlack(tree)) throw new Error("splitBlack"); return [tree.left, tree.elem, tree.right]; } function balance(left : RedBlackTree, elem : E, right : RedBlackTree) : RedBlackTree { const leftR = isRed(left); const rightR = isRed(right); let x : E let y : E let z : E let a : RedBlackTree let b : RedBlackTree let c : RedBlackTree let d : RedBlackTree if (leftR && rightR) { // (T R a x b) y (T R c z d) [a, x, b] = splitRed(left); y = elem; [c, z, d] = splitRed(right); } else if (leftR && isRed(left.left)) { // (T R (T R a x b) y c) z d [a, x, b] = splitRed(left.left); y = left.elem; c = left.right; z = elem; d = right; } else if (leftR && isRed(left.right)) { // (T R a x (T R b y c)) z d a = left.left; x = left.elem; [b, y, c] = splitRed(left.right); z = elem; d = right; } else if (rightR && isRed(right.right)) { // a x (T R b y (T R c z d)) a = left; x = elem; b = right.left; y = right.elem; [c, z, d] = splitRed(right.right); } else if (rightR && isRed(right.left)) { // a x (T R (T R b y c) z d) a = left; x = elem; [b, y, c] = splitRed(right.left); z = right.elem; d = right.right; } else { return mkBlack(left, elem, right); } return mkRed(mkBlack(a, x, b), y, mkBlack(c, z, d)); } /** * Inserts a new element into the tree. Returns undefined if the element is already part of the tree. */ export function insertElement(order : Compare, x : E, tree : RedBlackTree) : { result : RedBlackTree, previous : E | undefined } { let previous : E | undefined = undefined; function insert(tree : RedBlackTree) : RedBlackTree { if (isEmpty(tree)) return mkRed(empty(), x, empty()); const c = order.compare(x, tree.elem); switch(c) { case Relation.UNRELATED: throw new Error("RedBlackTree: Cannot compare '" + x + "' with '" + tree.elem + "'."); case Relation.EQUAL: { previous = tree.elem; if (isRed(tree)) return mkRed(tree.left, x, tree.right); // @ts-ignore else return mkBlack(tree.left, x, tree.right); } case Relation.LESS: { const left = insert(tree.left); if (isRed(tree)) return mkRed(left, tree.elem, tree.right); tree = tree as Black; return balance(left, tree.elem, tree.right); } case Relation.GREATER: { const right = insert(tree.right); if (isRed(tree)) return mkRed(tree.left, tree.elem, right); tree = tree as Black; return balance(tree.left, tree.elem, right); } default: assertNever(c); } } return { result: forceBlack(insert(tree)), previous: previous }; } /** * Deletes an existing element from the tree. Returns undefined if the element is already part of the tree. */ export function deleteElement(order : Compare, x : E, tree : RedBlackTree) : { result : RedBlackTree, deleted : E | undefined } { let deleted : E | undefined = undefined; function sub1(tree : RedBlackTree) : RedBlackTree { if (isBlack(tree)) return mkRed(tree.left, tree.elem, tree.right); else throw new Error("RedBlackTree: sub1 invariant failed."); } function balleft(left : RedBlackTree, elem : E, right : RedBlackTree) : RedBlackTree { if (isRed(left)) return mkRed(mkBlack(left.left, left.elem, left.right), elem, right); if (isBlack(right)) return balance(left, elem, mkRed(right.left, right.elem, right.right)); // @ts-ignore right should have type Red | null const [T, z, c] = splitRed(right as Red); const [a, y, b] = splitBlack(T as Black); return mkRed(mkBlack(left, elem, a), y, balance(b, z, sub1(c))); } function balright(left : RedBlackTree, elem : E, right : RedBlackTree) : RedBlackTree { if (isRed(right)) return mkRed(left, elem, mkBlack(right.left, right.elem, right.right)); if (isBlack(left)) return balance(mkRed(left.left, left.elem, left.right), elem, right); // @ts-ignore left should have type Red | null const [a, x, T] = splitRed(left as Red); const [b, y, c] = splitBlack(T as Black); return mkRed(balance(sub1(a), x, b), y, mkBlack(c, elem, right)); } function delformLeft(left : RedBlackTree, elem : E, right : RedBlackTree) : RedBlackTree { const l = del(left); if (isBlack(left)) return balleft(l, elem, right); else return mkRed(l, elem, right); } function delformRight(left : RedBlackTree, elem : E, right : RedBlackTree) : RedBlackTree { const r = del(right); if (isBlack(right)) return balright(left, elem, r); else return mkRed(left, elem, r); } function app(left : RedBlackTree, right : RedBlackTree) : RedBlackTree { if (isEmpty(left)) return right; if (isEmpty(right)) return left; if (isRed(left) && isRed(right)) { const [a, x, b] = splitRed(left); const [c, y, d] = splitRed(right); const bc = app(b, c); if (isRed(bc)) { const [b, z, c] = splitRed(bc); return mkRed(mkRed(a, x, b), z, mkRed(c, y, d)); } else return mkRed(a, x, mkRed(bc, y, d)); } if (isBlack(left) && isBlack(right)) { const [a, x, b] = splitBlack(left); const [c, y, d] = splitBlack(right); const bc = app(b, c); if (isRed(bc)) { const [b, z, c] = splitRed(bc); return mkRed(mkBlack(a, x, b), z, mkBlack(c, y, d)); } else return balleft(a, x, mkBlack(bc, y, d)); } if (isRed(right)) { const [b, x, c] = splitRed(right); return mkRed(app(left, b), x, c); } if (isRed(left)) { const [a, x, b] = splitRed(left); return mkRed(a, x, app(b, right)); } throw new Error("RedBlackTree.app: unreachable reached."); } function del(tree : RedBlackTree) : RedBlackTree { if (isEmpty(tree)) return empty(); const c = order.compare(x, tree.elem); switch(c) { case Relation.UNRELATED: throw new Error("RedBlackTree: Cannot compare '" + x + "' with '" + tree.elem + "'."); case Relation.LESS: return delformLeft(tree.left, tree.elem, tree.right); case Relation.GREATER: return delformRight(tree.left, tree.elem, tree.right); case Relation.EQUAL: { deleted = tree.elem; return app(tree.left, tree.right); } default: assertNever(c); } } return { result: forceBlack(del(tree)), deleted: deleted }; } export function* iterateElements(tree : RedBlackTree) : Generator { if (tree !== null) { yield* iterateElements(tree.left); yield tree.elem; yield* iterateElements(tree.right); } } export function blackHeight(tree : RedBlackTree) : nat { if (isEmpty(tree)) return 1; else if (isRed(tree)) return blackHeight(tree.left); else return blackHeight((tree as Black).left) + 1; }