Skip to content

Commit

Permalink
global memo fast
Browse files Browse the repository at this point in the history
  • Loading branch information
maxdeliso committed Feb 13, 2025
1 parent 8163a63 commit 98fd9ef
Show file tree
Hide file tree
Showing 19 changed files with 852 additions and 420 deletions.
295 changes: 295 additions & 0 deletions lib/data/avl/avlNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
/**
*
* https://en.wikipedia.org/wiki/AVL_tree
*
* An Adelson-Velsky and Landis (AVL) tree
* - `key`: The BST key
* - `value`: The value associated with that key
* - `height`: For balancing
* - `left` & `right`: Pointers to subtrees
*/
export interface AVLNode<TKey, TValue> {
readonly key: TKey;
readonly value: TValue;
readonly height: number;
readonly left: AVLNode<TKey, TValue> | null;
readonly right: AVLNode<TKey, TValue> | null;
}

/**
* An AVL tree is just a reference to the root node (or null if empty).
*/
export interface AVLTree<TKey, TValue> {
readonly root: AVLNode<TKey, TValue> | null;
}

/**
* Create an empty AVL tree.
*/
export function createEmptyAVL<TKey, TValue>(): AVLTree<TKey, TValue> {
return { root: null };
}

/**
* Create a new AVLNode (immutable).
*/
function createAVLNode<TKey, TValue>(
key: TKey,
value: TValue,
height: number,
left: AVLNode<TKey, TValue> | null,
right: AVLNode<TKey, TValue> | null
): AVLNode<TKey, TValue> {
return { key, value, height, left, right };
}

/** Get a node's height safely. */
function nodeHeight<TKey, TValue>(node: AVLNode<TKey, TValue> | null): number {
return node ? node.height : 0;
}


/** Recompute a node's height based on its children. */
function recalcHeight<TKey, TValue>(node: AVLNode<TKey, TValue>): AVLNode<TKey, TValue> {
const h = 1 + Math.max(nodeHeight(node.left), nodeHeight(node.right));
if (h === node.height) {
// no change
return node;
}
return createAVLNode(node.key, node.value, h, node.left, node.right);
}

/** Balance factor: height(left) - height(right). */
function balanceFactor<TKey, TValue>(node: AVLNode<TKey, TValue>): number {
return nodeHeight(node.left) - nodeHeight(node.right);
}

/** Right rotation. */
function rotateRight<TKey, TValue>(y: AVLNode<TKey, TValue>): AVLNode<TKey, TValue> {
const x = y.left;
if (!x) return y; // no rotation possible if no left child
const T2 = x.right;

// Perform rotation
const newY = createAVLNode(y.key, y.value, y.height, T2, y.right);
const newX = createAVLNode(x.key, x.value, x.height, x.left, newY);

// Recalc heights
const newY2 = recalcHeight(newY);
const newX2 = createAVLNode(newX.key, newX.value, newX.height, newX.left, newY2);
return recalcHeight(newX2);
}

/** Left rotation. */
function rotateLeft<TKey, TValue>(x: AVLNode<TKey, TValue>): AVLNode<TKey, TValue> {
const y = x.right;
if (!y) return x;
const T2 = y.left;

// Perform rotation
const newX = createAVLNode(x.key, x.value, x.height, x.left, T2);
const newY = createAVLNode(y.key, y.value, y.height, newX, y.right);

const newX2 = recalcHeight(newX);
const newY2 = createAVLNode(newY.key, newY.value, newY.height, newX2, newY.right);
return recalcHeight(newY2);
}

/**
* Insert `(key, value)` into the AVL tree (persistent, immutable).
*
* If `key` already exists in the tree, we overwrite its value.
*
* @param tree The original AVL tree.
* @param key The key to insert.
* @param value The value to insert.
* @param compareKeys A comparator for TKey. Return <0 if a<b, 0 if a==b, >0 if a>b.
* @returns A **new** AVL tree (the old one is not mutated).
*/
export function insertAVL<TKey, TValue>(
tree: AVLTree<TKey, TValue>,
key: TKey,
value: TValue,
compareKeys: (a: TKey, b: TKey) => number
): AVLTree<TKey, TValue> {
// Special case: empty tree -> new root
if (!tree.root) {
const newRoot = createAVLNode(key, value, 1, null, null);
return { root: newRoot };
}

interface StackItem {
node: AVLNode<TKey, TValue> | null;
direction: 'L' | 'R' | null;
}

const stack: StackItem[] = [];
let current: AVLNode<TKey, TValue> | null = tree.root;

for (;;) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!current) {
// We'll insert a new leaf here
stack.push({ node: null, direction: null });
break;
}

const cmp = compareKeys(key, current.key);
if (cmp === 0) {
// Overwrite
stack.push({ node: current, direction: null });
break;
} else if (cmp < 0) {
// Next move is to the left
stack.push({ node: current, direction: 'L' });
if (!current.left) {
stack.push({ node: null, direction: 'L' });
break;
}
current = current.left;
} else {
// Next move is to the right
stack.push({ node: current, direction: 'R' });
if (!current.right) {
stack.push({ node: null, direction: 'R' });
break;
}
current = current.right;
}
}

let newChild: AVLNode<TKey, TValue> | null = null;

// We have an insertion or overwrite scenario at the top of the stack
if (stack.length > 0) {
const top = stack[stack.length - 1];
if (top.node === null) {
// True insertion: create a new leaf node
newChild = createAVLNode(key, value, 1, null, null);
stack.pop();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if (top.node) {
// Overwrite
const node = top.node;
// Keep left/right, but update the node's value
const replaced = createAVLNode(node.key, value, node.height, node.left, node.right);
stack[stack.length - 1] = { node: replaced, direction: top.direction };
}
}

// Now bubble up, rebalancing if needed
let subtree = newChild;
while (stack.length > 0) {
const top = stack.pop();
if (!top?.node) {
continue;
}

const { node, direction } = top;
let updated = node;

if (subtree) {
// Attach subtree in the correct direction
const cmp = compareKeys(key, node.key);
if (cmp === 0) {
// we replaced this node entirely
updated = subtree;
} else {
if (direction === 'L') {
updated = createAVLNode(node.key, node.value, node.height, subtree, node.right);
} else if (direction === 'R') {
updated = createAVLNode(node.key, node.value, node.height, node.left, subtree);
}
}
}

updated = recalcHeight(updated);

// Check balance factor
const bf = balanceFactor(updated);
if (bf > 1) {
// left-heavy
if (compareKeys(key, updated.left?.key ?? node.key) < 0) {
// Left-Left
updated = rotateRight(updated);
} else {
// Left-Right
if (updated.left) {
const newLeft = rotateLeft(updated.left);
updated = createAVLNode(updated.key, updated.value, updated.height, newLeft, updated.right);
}
updated = rotateRight(updated);
}
} else if (bf < -1) {
// right-heavy
if (compareKeys(key, updated.right?.key ?? node.key) > 0) {
// Right-Right
updated = rotateLeft(updated);
} else {
// Right-Left
if (updated.right) {
const newRight = rotateRight(updated.right);
updated = createAVLNode(updated.key, updated.value, updated.height, updated.left, newRight);
}
updated = rotateLeft(updated);
}
}

subtree = recalcHeight(updated);
}

return { root: subtree };
}

/**
* Look up a key in the AVL tree. Returns the associated value or undefined if not found.
*/
export function searchAVL<TKey, TValue>(
tree: AVLTree<TKey, TValue>,
key: TKey,
compareKeys: (a: TKey, b: TKey) => number
): TValue | undefined {
let current = tree.root;
while (current) {
const cmp = compareKeys(key, current.key);
if (cmp === 0) {
return current.value;
} else if (cmp < 0) {
current = current.left;
} else {
current = current.right;
}
}
return undefined;
}

/**
* Return an array of all key-value pairs in ascending key order (in-order traversal).
*/
export function keyValuePairs<TKey, TValue>(
tree: AVLTree<TKey, TValue>
): [TKey, TValue][] {
const result: [TKey, TValue][] = [];
const stack: AVLNode<TKey, TValue>[] = [];

let current: AVLNode<TKey, TValue> | null = tree.root;

while (stack.length > 0 || current !== null) {
// Traverse left subtree
if (current !== null) {
stack.push(current);
current = current.left;
} else {
// Pop from stack, add the node's key-value to result,
// then move to the right subtree
const node = stack.pop();
if (node) {
current = node;
result.push([current.key, current.value]);
current = current.right;
}
}
}

return result;
}
46 changes: 46 additions & 0 deletions lib/data/map/skiMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
AVLTree,
createEmptyAVL,
insertAVL,
searchAVL
} from '../avl/avlNode.js';

import { SKIExpression, SKIKey } from '../../ski/expression.js';

function compareSKIKeys(a: SKIKey, b: SKIKey): number {
const len = Math.min(a.length, b.length);
for (let i = 0; i < len; i++) {
if (a[i] < b[i]) return -1;
if (a[i] > b[i]) return 1;
}
return a.length - b.length;
}

export type SKIMap = AVLTree<SKIKey, SKIExpression>;

export function createMap(): SKIMap {
return createEmptyAVL<SKIKey, SKIExpression>();
}

export function insertMap(
tree: SKIMap,
key: SKIKey,
value: SKIExpression
): SKIMap {
return insertAVL(
tree,
key,
value,
compareSKIKeys);
}

export function searchMap(
tree: SKIMap,
key: SKIKey
): SKIExpression | undefined {
return searchAVL(
tree,
key,
compareSKIKeys
);
}
31 changes: 31 additions & 0 deletions lib/data/map/stringMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { insertAVL, createEmptyAVL, searchAVL } from '../avl/avlNode.js';

import { AVLTree } from '../avl/avlNode.js';

export function compareStrings(a: string, b: string): number {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}

/** Create an empty string->string AVL tree. */
export function createStringMap(): AVLTree<string, string> {
return createEmptyAVL<string, string>();
}

/** Immutable insert into string->string map. */
export function insertStringMap(
tree: AVLTree<string, string>,
key: string,
value: string
): AVLTree<string, string> {
return insertAVL(tree, key, value, compareStrings);
}

/** Immutable search for string->string map. */
export function searchStringMap(
tree: AVLTree<string, string>,
key: string
): string | undefined {
return searchAVL(tree, key, compareStrings);
}
Loading

0 comments on commit 98fd9ef

Please sign in to comment.