std

Browse the standard library for Andromeda

collections/mod.ts
/**
 * @fileoverview Advanced data structures and collection utilities
 * @module collections
 */

/**
 * A generic queue implementation with FIFO semantics.
 *
 * @example
 * ```ts
 * const queue = new Queue<string>();
 * queue.enqueue('first');
 * queue.enqueue('second');
 * console.log(queue.dequeue()); // 'first'
 * console.log(queue.size); // 1
 * ```
 */
export class Queue<T> {
  #items: T[] = [];

  /**
   * Adds an item to the back of the queue.
   */
  enqueue(item: T): void {
    this.#items.push(item);
  }

  /**
   * Removes and returns the item from the front of the queue.
   */
  dequeue(): T | undefined {
    return this.#items.shift();
  }

  /**
   * Returns the item at the front of the queue without removing it.
   */
  peek(): T | undefined {
    return this.#items[0];
  }

  /**
   * Returns the number of items in the queue.
   */
  get size(): number {
    return this.#items.length;
  }

  /**
   * Returns true if the queue is empty.
   */
  get isEmpty(): boolean {
    return this.#items.length === 0;
  }

  /**
   * Removes all items from the queue.
   */
  clear(): void {
    this.#items = [];
  }

  /**
   * Returns an array containing all items in the queue.
   */
  toArray(): T[] {
    return [...this.#items];
  }

  /**
   * Makes the queue iterable.
   */
  *[Symbol.iterator](): Iterator<T> {
    for (const item of this.#items) {
      yield item;
    }
  }
}

/**
 * A generic stack implementation with LIFO semantics.
 *
 * @example
 * ```ts
 * const stack = new Stack<number>();
 * stack.push(1);
 * stack.push(2);
 * console.log(stack.pop()); // 2
 * console.log(stack.size); // 1
 * ```
 */
export class Stack<T> {
  #items: T[] = [];

  /**
   * Adds an item to the top of the stack.
   */
  push(item: T): void {
    this.#items.push(item);
  }

  /**
   * Removes and returns the item from the top of the stack.
   */
  pop(): T | undefined {
    return this.#items.pop();
  }

  /**
   * Returns the item at the top of the stack without removing it.
   */
  peek(): T | undefined {
    return this.#items[this.#items.length - 1];
  }

  /**
   * Returns the number of items in the stack.
   */
  get size(): number {
    return this.#items.length;
  }

  /**
   * Returns true if the stack is empty.
   */
  get isEmpty(): boolean {
    return this.#items.length === 0;
  }

  /**
   * Removes all items from the stack.
   */
  clear(): void {
    this.#items = [];
  }

  /**
   * Returns an array containing all items in the stack.
   */
  toArray(): T[] {
    return [...this.#items];
  }

  /**
   * Makes the stack iterable.
   */
  *[Symbol.iterator](): Iterator<T> {
    for (let i = this.#items.length - 1; i >= 0; i--) {
      yield this.#items[i]!;
    }
  }
}

/**
 * Node for linked list implementation.
 */
class ListNode<T> {
  value: T;
  next: ListNode<T> | null;

  constructor(
    value: T,
    next: ListNode<T> | null = null,
  ) {
    this.value = value;
    this.next = next;
  }
}

/**
 * A generic linked list implementation.
 *
 * @example
 * ```ts
 * const list = new LinkedList<string>();
 * list.append('first');
 * list.append('second');
 * list.prepend('zero');
 * console.log(list.toArray()); // ['zero', 'first', 'second']
 * ```
 */
export class LinkedList<T> {
  #head: ListNode<T> | null = null;
  #tail: ListNode<T> | null = null;
  #length = 0;

  /**
   * Adds an item to the end of the list.
   */
  append(value: T): void {
    const node = new ListNode(value);

    if (!this.#head) {
      this.#head = node;
      this.#tail = node;
    } else {
      this.#tail!.next = node;
      this.#tail = node;
    }

    this.#length++;
  }

  /**
   * Adds an item to the beginning of the list.
   */
  prepend(value: T): void {
    const node = new ListNode(value, this.#head);
    this.#head = node;

    if (!this.#tail) {
      this.#tail = node;
    }

    this.#length++;
  }

  /**
   * Inserts an item at the specified index.
   */
  insertAt(index: number, value: T): void {
    if (index < 0 || index > this.#length) {
      throw new Error("Index out of bounds");
    }

    if (index === 0) {
      this.prepend(value);
      return;
    }

    if (index === this.#length) {
      this.append(value);
      return;
    }

    const node = new ListNode(value);
    let current = this.#head!;

    for (let i = 0; i < index - 1; i++) {
      current = current.next!;
    }

    node.next = current.next;
    current.next = node;
    this.#length++;
  }

  /**
   * Removes the first occurrence of the specified value.
   */
  remove(value: T): boolean {
    if (!this.#head) return false;

    if (this.#head.value === value) {
      this.#head = this.#head.next;
      if (!this.#head) this.#tail = null;
      this.#length--;
      return true;
    }

    let current = this.#head;
    while (current.next) {
      if (current.next.value === value) {
        if (current.next === this.#tail) {
          this.#tail = current;
        }
        current.next = current.next.next;
        this.#length--;
        return true;
      }
      current = current.next;
    }

    return false;
  }

  /**
   * Removes the item at the specified index.
   */
  removeAt(index: number): T | undefined {
    if (index < 0 || index >= this.#length) return undefined;

    if (index === 0) {
      const value = this.#head!.value;
      this.#head = this.#head!.next;
      if (!this.#head) this.#tail = null;
      this.#length--;
      return value;
    }

    let current = this.#head!;
    for (let i = 0; i < index - 1; i++) {
      current = current.next!;
    }

    const value = current.next!.value;
    if (current.next === this.#tail) {
      this.#tail = current;
    }
    current.next = current.next!.next;
    this.#length--;

    return value;
  }

  /**
   * Returns the item at the specified index.
   */
  get(index: number): T | undefined {
    if (index < 0 || index >= this.#length) return undefined;

    let current = this.#head!;
    for (let i = 0; i < index; i++) {
      current = current.next!;
    }

    return current.value;
  }

  /**
   * Returns the index of the first occurrence of the specified value.
   */
  indexOf(value: T): number {
    let current = this.#head;
    let index = 0;

    while (current) {
      if (current.value === value) return index;
      current = current.next;
      index++;
    }

    return -1;
  }

  /**
   * Returns true if the list contains the specified value.
   */
  contains(value: T): boolean {
    return this.indexOf(value) !== -1;
  }

  /**
   * Returns the number of items in the list.
   */
  get size(): number {
    return this.#length;
  }

  /**
   * Returns true if the list is empty.
   */
  get isEmpty(): boolean {
    return this.#length === 0;
  }

  /**
   * Removes all items from the list.
   */
  clear(): void {
    this.#head = null;
    this.#tail = null;
    this.#length = 0;
  }

  /**
   * Returns an array containing all items in the list.
   */
  toArray(): T[] {
    const result: T[] = [];
    let current = this.#head;

    while (current) {
      result.push(current.value);
      current = current.next;
    }

    return result;
  }

  /**
   * Makes the list iterable.
   */
  *[Symbol.iterator](): Iterator<T> {
    let current = this.#head;
    while (current) {
      yield current.value;
      current = current.next;
    }
  }
}

/**
 * Node for binary tree implementation.
 */
class TreeNode<T> {
  value: T;
  left: TreeNode<T> | null = null;
  right: TreeNode<T> | null = null;
  constructor(
    value: T,
    left: TreeNode<T> | null = null,
    right: TreeNode<T> | null = null,
  ) {
    this.value = value;
    this.left = left;
    this.right = right;
  }
}

/**
 * A generic binary search tree implementation.
 *
 * @example
 * ```ts
 * const tree = new BinaryTree<number>();
 * tree.insert(5);
 * tree.insert(3);
 * tree.insert(7);
 * console.log(tree.contains(3)); // true
 * console.log(tree.inOrder()); // [3, 5, 7]
 * ```
 */
export class BinaryTree<T> {
  #root: TreeNode<T> | null = null;
  #compareFunction: (a: T, b: T) => number;

  constructor(compareFn?: (a: T, b: T) => number) {
    this.#compareFunction = compareFn || ((a, b) => {
      if (a < b) return -1;
      if (a > b) return 1;
      return 0;
    });
  }

  /**
   * Inserts a value into the tree.
   */
  insert(value: T): void {
    this.#root = this.#insertNode(this.#root, value);
  }

  #insertNode(node: TreeNode<T> | null, value: T): TreeNode<T> {
    if (!node) {
      return new TreeNode(value);
    }

    const comparison = this.#compareFunction(value, node.value);

    if (comparison < 0) {
      node.left = this.#insertNode(node.left, value);
    } else if (comparison > 0) {
      node.right = this.#insertNode(node.right, value);
    }

    return node;
  }

  /**
   * Removes a value from the tree.
   */
  remove(value: T): boolean {
    const originalSize = this.size;
    this.#root = this.#removeNode(this.#root, value);
    return this.size < originalSize;
  }

  #removeNode(node: TreeNode<T> | null, value: T): TreeNode<T> | null {
    if (!node) return null;

    const comparison = this.#compareFunction(value, node.value);

    if (comparison < 0) {
      node.left = this.#removeNode(node.left, value);
    } else if (comparison > 0) {
      node.right = this.#removeNode(node.right, value);
    } else {
      // Node to be removed found
      if (!node.left) return node.right;
      if (!node.right) return node.left;

      // Node has two children
      const minRight = this.#findMin(node.right);
      node.value = minRight.value;
      node.right = this.#removeNode(node.right, minRight.value);
    }

    return node;
  }

  #findMin(node: TreeNode<T>): TreeNode<T> {
    while (node.left) {
      node = node.left;
    }
    return node;
  }

  /**
   * Returns true if the tree contains the specified value.
   */
  contains(value: T): boolean {
    return this.#search(this.#root, value) !== null;
  }

  #search(node: TreeNode<T> | null, value: T): TreeNode<T> | null {
    if (!node) return null;

    const comparison = this.#compareFunction(value, node.value);

    if (comparison === 0) return node;
    if (comparison < 0) return this.#search(node.left, value);
    return this.#search(node.right, value);
  }

  /**
   * Returns the number of nodes in the tree.
   */
  get size(): number {
    return this.#getSize(this.#root);
  }

  #getSize(node: TreeNode<T> | null): number {
    if (!node) return 0;
    return 1 + this.#getSize(node.left) + this.#getSize(node.right);
  }

  /**
   * Returns true if the tree is empty.
   */
  get isEmpty(): boolean {
    return this.#root === null;
  }

  /**
   * Removes all nodes from the tree.
   */
  clear(): void {
    this.#root = null;
  }

  /**
   * Returns an array of values in in-order traversal.
   */
  inOrder(): T[] {
    const result: T[] = [];
    this.#inOrderTraversal(this.#root, result);
    return result;
  }

  #inOrderTraversal(node: TreeNode<T> | null, result: T[]): void {
    if (node) {
      this.#inOrderTraversal(node.left, result);
      result.push(node.value);
      this.#inOrderTraversal(node.right, result);
    }
  }

  /**
   * Returns an array of values in pre-order traversal.
   */
  preOrder(): T[] {
    const result: T[] = [];
    this.#preOrderTraversal(this.#root, result);
    return result;
  }

  #preOrderTraversal(node: TreeNode<T> | null, result: T[]): void {
    if (node) {
      result.push(node.value);
      this.#preOrderTraversal(node.left, result);
      this.#preOrderTraversal(node.right, result);
    }
  }

  /**
   * Returns an array of values in post-order traversal.
   */
  postOrder(): T[] {
    const result: T[] = [];
    this.#postOrderTraversal(this.#root, result);
    return result;
  }

  #postOrderTraversal(node: TreeNode<T> | null, result: T[]): void {
    if (node) {
      this.#postOrderTraversal(node.left, result);
      this.#postOrderTraversal(node.right, result);
      result.push(node.value);
    }
  }
}

/**
 * A generic min-heap implementation.
 *
 * @example
 * ```ts
 * const heap = new Heap<number>();
 * heap.insert(5);
 * heap.insert(3);
 * heap.insert(7);
 * console.log(heap.extractMin()); // 3
 * console.log(heap.peek()); // 5
 * ```
 */
export class Heap<T> {
  #items: T[] = [];
  #compareFunction: (a: T, b: T) => number;

  constructor(compareFn?: (a: T, b: T) => number) {
    this.#compareFunction = compareFn || ((a, b) => {
      if (a < b) return -1;
      if (a > b) return 1;
      return 0;
    });
  }

  /**
   * Inserts a value into the heap.
   */
  insert(value: T): void {
    this.#items.push(value);
    this.#heapifyUp(this.#items.length - 1);
  }

  /**
   * Removes and returns the minimum (or maximum) value from the heap.
   */
  extractMin(): T | undefined {
    if (this.#items.length === 0) return undefined;
    if (this.#items.length === 1) return this.#items.pop();

    const min = this.#items[0];
    this.#items[0] = this.#items.pop()!;
    this.#heapifyDown(0);

    return min;
  }

  /**
   * Returns the minimum (or maximum) value without removing it.
   */
  peek(): T | undefined {
    return this.#items[0];
  }

  /**
   * Returns the number of items in the heap.
   */
  get size(): number {
    return this.#items.length;
  }

  /**
   * Returns true if the heap is empty.
   */
  get isEmpty(): boolean {
    return this.#items.length === 0;
  }

  /**
   * Removes all items from the heap.
   */
  clear(): void {
    this.#items = [];
  }

  /**
   * Returns an array containing all items in the heap.
   */
  toArray(): T[] {
    return [...this.#items];
  }

  #heapifyUp(index: number): void {
    if (index === 0) return;

    const parentIndex = Math.floor((index - 1) / 2);

    if (
      this.#compareFunction(this.#items[index]!, this.#items[parentIndex]!) <
        0
    ) {
      [this.#items[index], this.#items[parentIndex]] = [
        this.#items[parentIndex]!,
        this.#items[index]!,
      ];
      this.#heapifyUp(parentIndex);
    }
  }

  #heapifyDown(index: number): void {
    const leftChild = 2 * index + 1;
    const rightChild = 2 * index + 2;
    let smallest = index;

    if (
      leftChild < this.#items.length &&
      this.#compareFunction(
          this.#items[leftChild]!,
          this.#items[smallest]!,
        ) < 0
    ) {
      smallest = leftChild;
    }

    if (
      rightChild < this.#items.length &&
      this.#compareFunction(
          this.#items[rightChild]!,
          this.#items[smallest]!,
        ) < 0
    ) {
      smallest = rightChild;
    }

    if (smallest !== index) {
      [this.#items[index], this.#items[smallest]] = [
        this.#items[smallest]!,
        this.#items[index]!,
      ];
      this.#heapifyDown(smallest);
    }
  }
}

/**
 * A Least Recently Used (LRU) cache implementation.
 *
 * @example
 * ```ts
 * const cache = new LRUCache<string, number>(2);
 * cache.set('a', 1);
 * cache.set('b', 2);
 * cache.set('c', 3); // 'a' is evicted
 * console.log(cache.get('a')); // undefined
 * console.log(cache.get('b')); // 2
 * ```
 */
export class LRUCache<K, V> {
  #capacity: number;
  #cache = new Map<K, V>();

  constructor(capacity: number) {
    if (capacity <= 0) {
      throw new Error("Capacity must be greater than 0");
    }
    this.#capacity = capacity;
  }

  /**
   * Gets a value from the cache.
   */
  get(key: K): V | undefined {
    if (this.#cache.has(key)) {
      const value = this.#cache.get(key)!;
      // Move to end (most recently used)
      this.#cache.delete(key);
      this.#cache.set(key, value);
      return value;
    }
    return undefined;
  }

  /**
   * Sets a value in the cache.
   */
  set(key: K, value: V): void {
    if (this.#cache.has(key)) {
      // Update existing key
      this.#cache.delete(key);
    } else if (this.#cache.size >= this.#capacity) {
      // Remove least recently used (first item)
      const firstKey = this.#cache.keys().next().value as K;
      if (firstKey !== undefined) {
        this.#cache.delete(firstKey);
      }
    }

    this.#cache.set(key, value);
  }

  /**
   * Removes a key from the cache.
   */
  delete(key: K): boolean {
    return this.#cache.delete(key);
  }

  /**
   * Returns true if the cache contains the key.
   */
  has(key: K): boolean {
    return this.#cache.has(key);
  }

  /**
   * Returns the number of items in the cache.
   */
  get size(): number {
    return this.#cache.size;
  }

  /**
   * Removes all items from the cache.
   */
  clear(): void {
    this.#cache.clear();
  }

  /**
   * Returns an array of all keys in the cache.
   */
  keys(): K[] {
    return Array.from(this.#cache.keys());
  }

  /**
   * Returns an array of all values in the cache.
   */
  values(): V[] {
    return Array.from(this.#cache.values());
  }
}