1
0
mirror of https://github.com/robonen/tools.git synced 2026-03-20 10:54:44 +00:00

feat(monorepo): migrate vue packages and apply oxlint refactors

This commit is contained in:
2026-03-07 18:07:22 +07:00
parent abd6605db3
commit 41d5e18f6b
286 changed files with 10295 additions and 5028 deletions

View File

@@ -3,227 +3,227 @@ import { describe, expect, it } from 'vitest';
import { BinaryHeap } from '.';
describe('BinaryHeap', () => {
describe('constructor', () => {
it('should create an empty heap', () => {
const heap = new BinaryHeap<number>();
describe('constructor', () => {
it('should create an empty heap', () => {
const heap = new BinaryHeap<number>();
expect(heap.length).toBe(0);
expect(heap.isEmpty).toBe(true);
});
it('should create a heap from single value', () => {
const heap = new BinaryHeap(42);
expect(heap.length).toBe(1);
expect(heap.peek()).toBe(42);
});
it('should create a heap from array (heapify)', () => {
const heap = new BinaryHeap([5, 3, 8, 1, 4]);
expect(heap.length).toBe(5);
expect(heap.peek()).toBe(1);
});
it('should accept a custom comparator for max-heap', () => {
const heap = new BinaryHeap([5, 3, 8, 1, 4], {
comparator: (a, b) => b - a,
});
expect(heap.peek()).toBe(8);
});
expect(heap.length).toBe(0);
expect(heap.isEmpty).toBe(true);
});
describe('push', () => {
it('should insert elements maintaining heap property', () => {
const heap = new BinaryHeap<number>();
it('should create a heap from single value', () => {
const heap = new BinaryHeap(42);
heap.push(5);
heap.push(3);
heap.push(8);
heap.push(1);
expect(heap.peek()).toBe(1);
expect(heap.length).toBe(4);
});
it('should handle duplicate values', () => {
const heap = new BinaryHeap<number>();
heap.push(3);
heap.push(3);
heap.push(3);
expect(heap.length).toBe(3);
expect(heap.peek()).toBe(3);
});
expect(heap.length).toBe(1);
expect(heap.peek()).toBe(42);
});
describe('pop', () => {
it('should return undefined for empty heap', () => {
const heap = new BinaryHeap<number>();
it('should create a heap from array (heapify)', () => {
const heap = new BinaryHeap([5, 3, 8, 1, 4]);
expect(heap.pop()).toBeUndefined();
});
it('should extract elements in min-heap order', () => {
const heap = new BinaryHeap([5, 3, 8, 1, 4, 2, 7, 6]);
const sorted: number[] = [];
while (!heap.isEmpty) {
sorted.push(heap.pop()!);
}
expect(sorted).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
});
it('should extract elements in max-heap order with custom comparator', () => {
const heap = new BinaryHeap([5, 3, 8, 1, 4], {
comparator: (a, b) => b - a,
});
const sorted: number[] = [];
while (!heap.isEmpty) {
sorted.push(heap.pop()!);
}
expect(sorted).toEqual([8, 5, 4, 3, 1]);
});
it('should handle single element', () => {
const heap = new BinaryHeap(42);
expect(heap.pop()).toBe(42);
expect(heap.isEmpty).toBe(true);
});
expect(heap.length).toBe(5);
expect(heap.peek()).toBe(1);
});
describe('peek', () => {
it('should return undefined for empty heap', () => {
const heap = new BinaryHeap<number>();
it('should accept a custom comparator for max-heap', () => {
const heap = new BinaryHeap([5, 3, 8, 1, 4], {
comparator: (a, b) => b - a,
});
expect(heap.peek()).toBeUndefined();
});
expect(heap.peek()).toBe(8);
});
});
it('should return root without removing it', () => {
const heap = new BinaryHeap([5, 3, 1]);
describe('push', () => {
it('should insert elements maintaining heap property', () => {
const heap = new BinaryHeap<number>();
expect(heap.peek()).toBe(1);
expect(heap.length).toBe(3);
});
heap.push(5);
heap.push(3);
heap.push(8);
heap.push(1);
expect(heap.peek()).toBe(1);
expect(heap.length).toBe(4);
});
describe('clear', () => {
it('should remove all elements', () => {
const heap = new BinaryHeap([1, 2, 3]);
it('should handle duplicate values', () => {
const heap = new BinaryHeap<number>();
const result = heap.clear();
heap.push(3);
heap.push(3);
heap.push(3);
expect(heap.length).toBe(0);
expect(heap.isEmpty).toBe(true);
expect(result).toBe(heap);
});
expect(heap.length).toBe(3);
expect(heap.peek()).toBe(3);
});
});
describe('pop', () => {
it('should return undefined for empty heap', () => {
const heap = new BinaryHeap<number>();
expect(heap.pop()).toBeUndefined();
});
describe('toArray', () => {
it('should return empty array for empty heap', () => {
const heap = new BinaryHeap<number>();
it('should extract elements in min-heap order', () => {
const heap = new BinaryHeap([5, 3, 8, 1, 4, 2, 7, 6]);
const sorted: number[] = [];
expect(heap.toArray()).toEqual([]);
});
while (!heap.isEmpty) {
sorted.push(heap.pop()!);
}
it('should return a shallow copy', () => {
const heap = new BinaryHeap([3, 1, 2]);
const arr = heap.toArray();
arr.push(99);
expect(heap.length).toBe(3);
});
expect(sorted).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
});
describe('toString', () => {
it('should return formatted string', () => {
const heap = new BinaryHeap([1, 2, 3]);
it('should extract elements in max-heap order with custom comparator', () => {
const heap = new BinaryHeap([5, 3, 8, 1, 4], {
comparator: (a, b) => b - a,
});
const sorted: number[] = [];
expect(heap.toString()).toBe('BinaryHeap(3)');
});
while (!heap.isEmpty) {
sorted.push(heap.pop()!);
}
expect(sorted).toEqual([8, 5, 4, 3, 1]);
});
describe('iterator', () => {
it('should iterate over heap elements', () => {
const heap = new BinaryHeap([5, 3, 8, 1]);
const elements = [...heap];
it('should handle single element', () => {
const heap = new BinaryHeap(42);
expect(elements.length).toBe(4);
expect(elements[0]).toBe(1);
});
expect(heap.pop()).toBe(42);
expect(heap.isEmpty).toBe(true);
});
});
describe('peek', () => {
it('should return undefined for empty heap', () => {
const heap = new BinaryHeap<number>();
expect(heap.peek()).toBeUndefined();
});
describe('custom comparator', () => {
it('should work with string length comparator', () => {
const heap = new BinaryHeap(['banana', 'apple', 'kiwi', 'fig'], {
comparator: (a, b) => a.length - b.length,
});
it('should return root without removing it', () => {
const heap = new BinaryHeap([5, 3, 1]);
expect(heap.pop()).toBe('fig');
expect(heap.pop()).toBe('kiwi');
});
expect(heap.peek()).toBe(1);
expect(heap.length).toBe(3);
});
});
it('should work with object comparator', () => {
interface Task {
priority: number;
name: string;
}
describe('clear', () => {
it('should remove all elements', () => {
const heap = new BinaryHeap([1, 2, 3]);
const heap = new BinaryHeap<Task>(
[
{ priority: 3, name: 'low' },
{ priority: 1, name: 'high' },
{ priority: 2, name: 'medium' },
],
{ comparator: (a, b) => a.priority - b.priority },
);
const result = heap.clear();
expect(heap.pop()?.name).toBe('high');
expect(heap.pop()?.name).toBe('medium');
expect(heap.pop()?.name).toBe('low');
});
expect(heap.length).toBe(0);
expect(heap.isEmpty).toBe(true);
expect(result).toBe(heap);
});
});
describe('toArray', () => {
it('should return empty array for empty heap', () => {
const heap = new BinaryHeap<number>();
expect(heap.toArray()).toEqual([]);
});
describe('heapify', () => {
it('should correctly heapify large arrays', () => {
const values = Array.from({ length: 1000 }, () => Math.random() * 1000 | 0);
const heap = new BinaryHeap(values);
const sorted: number[] = [];
it('should return a shallow copy', () => {
const heap = new BinaryHeap([3, 1, 2]);
const arr = heap.toArray();
while (!heap.isEmpty) {
sorted.push(heap.pop()!);
}
arr.push(99);
const expected = [...values].sort((a, b) => a - b);
expect(heap.length).toBe(3);
});
});
expect(sorted).toEqual(expected);
});
describe('toString', () => {
it('should return formatted string', () => {
const heap = new BinaryHeap([1, 2, 3]);
expect(heap.toString()).toBe('BinaryHeap(3)');
});
});
describe('iterator', () => {
it('should iterate over heap elements', () => {
const heap = new BinaryHeap([5, 3, 8, 1]);
const elements = [...heap];
expect(elements.length).toBe(4);
expect(elements[0]).toBe(1);
});
});
describe('custom comparator', () => {
it('should work with string length comparator', () => {
const heap = new BinaryHeap(['banana', 'apple', 'kiwi', 'fig'], {
comparator: (a, b) => a.length - b.length,
});
expect(heap.pop()).toBe('fig');
expect(heap.pop()).toBe('kiwi');
});
describe('interleaved operations', () => {
it('should maintain heap property with mixed push and pop', () => {
const heap = new BinaryHeap<number>();
it('should work with object comparator', () => {
interface Task {
priority: number;
name: string;
}
heap.push(10);
heap.push(5);
expect(heap.pop()).toBe(5);
const heap = new BinaryHeap<Task>(
[
{ priority: 3, name: 'low' },
{ priority: 1, name: 'high' },
{ priority: 2, name: 'medium' },
],
{ comparator: (a, b) => a.priority - b.priority },
);
heap.push(3);
heap.push(7);
expect(heap.pop()).toBe(3);
heap.push(1);
expect(heap.pop()).toBe(1);
expect(heap.pop()).toBe(7);
expect(heap.pop()).toBe(10);
expect(heap.pop()).toBeUndefined();
});
expect(heap.pop()?.name).toBe('high');
expect(heap.pop()?.name).toBe('medium');
expect(heap.pop()?.name).toBe('low');
});
});
describe('heapify', () => {
it('should correctly heapify large arrays', () => {
const values = Array.from({ length: 1000 }, () => Math.random() * 1000 | 0);
const heap = new BinaryHeap(values);
const sorted: number[] = [];
while (!heap.isEmpty) {
sorted.push(heap.pop()!);
}
const expected = [...values].sort((a, b) => a - b);
expect(sorted).toEqual(expected);
});
});
describe('interleaved operations', () => {
it('should maintain heap property with mixed push and pop', () => {
const heap = new BinaryHeap<number>();
heap.push(10);
heap.push(5);
expect(heap.pop()).toBe(5);
heap.push(3);
heap.push(7);
expect(heap.pop()).toBe(3);
heap.push(1);
expect(heap.pop()).toBe(1);
expect(heap.pop()).toBe(7);
expect(heap.pop()).toBe(10);
expect(heap.pop()).toBeUndefined();
});
});
});

View File

@@ -5,7 +5,7 @@ import type { BinaryHeapLike, Comparator } from './types';
export type { BinaryHeapLike, Comparator } from './types';
export interface BinaryHeapOptions<T> {
comparator?: Comparator<T>;
comparator?: Comparator<T>;
}
/**
@@ -27,194 +27,194 @@ const defaultComparator: Comparator<any> = (a: number, b: number) => a - b;
* @template T The type of elements stored in the heap
*/
export class BinaryHeap<T> implements BinaryHeapLike<T> {
/**
/**
* The comparator function used to order elements
*
* @private
* @type {Comparator<T>}
*/
private readonly comparator: Comparator<T>;
private readonly comparator: Comparator<T>;
/**
/**
* Internal flat array backing the heap
*
* @private
* @type {T[]}
*/
private readonly heap: T[] = [];
private readonly heap: T[] = [];
/**
/**
* Creates an instance of BinaryHeap
*
* @param {(T[] | T)} [initialValues] The initial values to heapify
* @param {BinaryHeapOptions<T>} [options] Heap configuration
*/
constructor(initialValues?: T[] | T, options?: BinaryHeapOptions<T>) {
this.comparator = options?.comparator ?? defaultComparator;
constructor(initialValues?: T[] | T, options?: BinaryHeapOptions<T>) {
this.comparator = options?.comparator ?? defaultComparator;
if (initialValues !== null && initialValues !== undefined) {
const items = isArray(initialValues) ? initialValues : [initialValues];
this.heap.push(...items);
this.heapify();
}
if (initialValues !== null && initialValues !== undefined) {
const items = isArray(initialValues) ? initialValues : [initialValues];
this.heap.push(...items);
this.heapify();
}
}
/**
/**
* Gets the number of elements in the heap
* @returns {number} The number of elements in the heap
*/
public get length(): number {
return this.heap.length;
}
public get length(): number {
return this.heap.length;
}
/**
/**
* Checks if the heap is empty
* @returns {boolean} `true` if the heap is empty, `false` otherwise
*/
public get isEmpty(): boolean {
return this.heap.length === 0;
}
public get isEmpty(): boolean {
return this.heap.length === 0;
}
/**
/**
* Pushes an element into the heap
* @param {T} element The element to insert
*/
public push(element: T): void {
this.heap.push(element);
this.siftUp(this.heap.length - 1);
}
public push(element: T): void {
this.heap.push(element);
this.siftUp(this.heap.length - 1);
}
/**
/**
* Removes and returns the root element (min or max depending on comparator)
* @returns {T | undefined} The root element, or `undefined` if the heap is empty
*/
public pop(): T | undefined {
if (this.heap.length === 0) return undefined;
public pop(): T | undefined {
if (this.heap.length === 0) return undefined;
const root = first(this.heap)!;
const last = this.heap.pop()!;
const root = first(this.heap)!;
const last = this.heap.pop()!;
if (this.heap.length > 0) {
this.heap[0] = last;
this.siftDown(0);
}
return root;
if (this.heap.length > 0) {
this.heap[0] = last;
this.siftDown(0);
}
/**
return root;
}
/**
* Returns the root element without removing it
* @returns {T | undefined} The root element, or `undefined` if the heap is empty
*/
public peek(): T | undefined {
return first(this.heap);
}
public peek(): T | undefined {
return first(this.heap);
}
/**
/**
* Removes all elements from the heap
* @returns {this} The heap instance for chaining
*/
public clear(): this {
this.heap.length = 0;
return this;
}
public clear(): this {
this.heap.length = 0;
return this;
}
/**
/**
* Returns a shallow copy of the heap elements as an array (heap order, not sorted)
* @returns {T[]} Array of elements in heap order
*/
public toArray(): T[] {
return this.heap.slice();
}
public toArray(): T[] {
return this.heap.slice();
}
/**
/**
* Returns a string representation of the heap
* @returns {string} String representation
*/
public toString(): string {
return `BinaryHeap(${this.heap.length})`;
}
public toString(): string {
return `BinaryHeap(${this.heap.length})`;
}
/**
/**
* Iterator over heap elements in heap order
*/
public *[Symbol.iterator](): Iterator<T> {
yield* this.heap;
}
public* [Symbol.iterator](): Iterator<T> {
yield* this.heap;
}
/**
/**
* Async iterator over heap elements in heap order
*/
public async *[Symbol.asyncIterator](): AsyncIterator<T> {
for (const element of this.heap)
yield element;
}
public async* [Symbol.asyncIterator](): AsyncIterator<T> {
for (const element of this.heap)
yield element;
}
/**
/**
* Restores heap property by sifting an element up
*
* @private
* @param {number} index The index of the element to sift up
*/
private siftUp(index: number): void {
const heap = this.heap;
const cmp = this.comparator;
private siftUp(index: number): void {
const heap = this.heap;
const cmp = this.comparator;
while (index > 0) {
const parent = (index - 1) >> 1;
while (index > 0) {
const parent = (index - 1) >> 1;
if (cmp(heap[index]!, heap[parent]!) >= 0) break;
if (cmp(heap[index]!, heap[parent]!) >= 0) break;
const temp = heap[index]!;
heap[index] = heap[parent]!;
heap[parent] = temp;
const temp = heap[index]!;
heap[index] = heap[parent]!;
heap[parent] = temp;
index = parent;
}
index = parent;
}
}
/**
/**
* Restores heap property by sifting an element down
*
* @private
* @param {number} index The index of the element to sift down
*/
private siftDown(index: number): void {
const heap = this.heap;
const cmp = this.comparator;
const length = heap.length;
private siftDown(index: number): void {
const heap = this.heap;
const cmp = this.comparator;
const length = heap.length;
while (true) {
let smallest = index;
const left = 2 * index + 1;
const right = 2 * index + 2;
while (true) {
let smallest = index;
const left = 2 * index + 1;
const right = 2 * index + 2;
if (left < length && cmp(heap[left]!, heap[smallest]!) < 0) {
smallest = left;
}
if (left < length && cmp(heap[left]!, heap[smallest]!) < 0) {
smallest = left;
}
if (right < length && cmp(heap[right]!, heap[smallest]!) < 0) {
smallest = right;
}
if (right < length && cmp(heap[right]!, heap[smallest]!) < 0) {
smallest = right;
}
if (smallest === index) break;
if (smallest === index) break;
const temp = heap[index]!;
heap[index] = heap[smallest]!;
heap[smallest] = temp;
const temp = heap[index]!;
heap[index] = heap[smallest]!;
heap[smallest] = temp;
index = smallest;
}
index = smallest;
}
}
/**
/**
* Builds heap from unordered array in O(n) using Floyd's algorithm
*
* @private
*/
private heapify(): void {
for (let i = (this.heap.length >> 1) - 1; i >= 0; i--) {
this.siftDown(i);
}
private heapify(): void {
for (let i = (this.heap.length >> 1) - 1; i >= 0; i--) {
this.siftDown(i);
}
}
}

View File

@@ -234,7 +234,7 @@ export class CircularBuffer<T> implements CircularBufferLike<T> {
*
* @returns {AsyncIterableIterator<T>}
*/
async *[Symbol.asyncIterator]() {
async* [Symbol.asyncIterator]() {
for (const element of this)
yield element;
}

View File

@@ -173,7 +173,7 @@ export class Deque<T> implements DequeLike<T> {
*
* @returns {AsyncIterableIterator<T>}
*/
async *[Symbol.asyncIterator]() {
async* [Symbol.asyncIterator]() {
for (const element of this.buffer)
yield element;
}

View File

@@ -3,404 +3,404 @@ import { describe, expect, it } from 'vitest';
import { LinkedList } from '.';
describe('LinkedList', () => {
describe('constructor', () => {
it('should create an empty list', () => {
const list = new LinkedList<number>();
describe('constructor', () => {
it('should create an empty list', () => {
const list = new LinkedList<number>();
expect(list.length).toBe(0);
expect(list.isEmpty).toBe(true);
expect(list.head).toBeUndefined();
expect(list.tail).toBeUndefined();
});
it('should create a list from single value', () => {
const list = new LinkedList(42);
expect(list.length).toBe(1);
expect(list.peekFront()).toBe(42);
expect(list.peekBack()).toBe(42);
});
it('should create a list from array', () => {
const list = new LinkedList([1, 2, 3]);
expect(list.length).toBe(3);
expect(list.peekFront()).toBe(1);
expect(list.peekBack()).toBe(3);
});
expect(list.length).toBe(0);
expect(list.isEmpty).toBe(true);
expect(list.head).toBeUndefined();
expect(list.tail).toBeUndefined();
});
describe('pushBack', () => {
it('should append to empty list', () => {
const list = new LinkedList<number>();
it('should create a list from single value', () => {
const list = new LinkedList(42);
const node = list.pushBack(1);
expect(list.length).toBe(1);
expect(node.value).toBe(1);
expect(list.head).toBe(node);
expect(list.tail).toBe(node);
});
it('should append to non-empty list', () => {
const list = new LinkedList([1, 2]);
list.pushBack(3);
expect(list.length).toBe(3);
expect(list.peekBack()).toBe(3);
expect(list.peekFront()).toBe(1);
});
it('should return the created node', () => {
const list = new LinkedList<number>();
const node = list.pushBack(5);
expect(node.value).toBe(5);
expect(node.prev).toBeUndefined();
expect(node.next).toBeUndefined();
});
expect(list.length).toBe(1);
expect(list.peekFront()).toBe(42);
expect(list.peekBack()).toBe(42);
});
describe('pushFront', () => {
it('should prepend to empty list', () => {
const list = new LinkedList<number>();
it('should create a list from array', () => {
const list = new LinkedList([1, 2, 3]);
const node = list.pushFront(1);
expect(list.length).toBe(3);
expect(list.peekFront()).toBe(1);
expect(list.peekBack()).toBe(3);
});
});
expect(list.length).toBe(1);
expect(list.head).toBe(node);
expect(list.tail).toBe(node);
});
describe('pushBack', () => {
it('should append to empty list', () => {
const list = new LinkedList<number>();
it('should prepend to non-empty list', () => {
const list = new LinkedList([2, 3]);
const node = list.pushBack(1);
list.pushFront(1);
expect(list.length).toBe(3);
expect(list.peekFront()).toBe(1);
expect(list.peekBack()).toBe(3);
});
expect(list.length).toBe(1);
expect(node.value).toBe(1);
expect(list.head).toBe(node);
expect(list.tail).toBe(node);
});
describe('popBack', () => {
it('should return undefined for empty list', () => {
const list = new LinkedList<number>();
it('should append to non-empty list', () => {
const list = new LinkedList([1, 2]);
expect(list.popBack()).toBeUndefined();
});
list.pushBack(3);
it('should remove and return last value', () => {
const list = new LinkedList([1, 2, 3]);
expect(list.popBack()).toBe(3);
expect(list.length).toBe(2);
expect(list.peekBack()).toBe(2);
});
it('should handle single element', () => {
const list = new LinkedList(1);
expect(list.popBack()).toBe(1);
expect(list.isEmpty).toBe(true);
expect(list.head).toBeUndefined();
expect(list.tail).toBeUndefined();
});
expect(list.length).toBe(3);
expect(list.peekBack()).toBe(3);
expect(list.peekFront()).toBe(1);
});
describe('popFront', () => {
it('should return undefined for empty list', () => {
const list = new LinkedList<number>();
it('should return the created node', () => {
const list = new LinkedList<number>();
expect(list.popFront()).toBeUndefined();
});
const node = list.pushBack(5);
it('should remove and return first value', () => {
const list = new LinkedList([1, 2, 3]);
expect(node.value).toBe(5);
expect(node.prev).toBeUndefined();
expect(node.next).toBeUndefined();
});
});
expect(list.popFront()).toBe(1);
expect(list.length).toBe(2);
expect(list.peekFront()).toBe(2);
});
describe('pushFront', () => {
it('should prepend to empty list', () => {
const list = new LinkedList<number>();
it('should handle single element', () => {
const list = new LinkedList(1);
const node = list.pushFront(1);
expect(list.popFront()).toBe(1);
expect(list.isEmpty).toBe(true);
expect(list.head).toBeUndefined();
expect(list.tail).toBeUndefined();
});
expect(list.length).toBe(1);
expect(list.head).toBe(node);
expect(list.tail).toBe(node);
});
describe('peekBack', () => {
it('should return undefined for empty list', () => {
const list = new LinkedList<number>();
it('should prepend to non-empty list', () => {
const list = new LinkedList([2, 3]);
expect(list.peekBack()).toBeUndefined();
});
list.pushFront(1);
it('should return last value without removing', () => {
const list = new LinkedList([1, 2, 3]);
expect(list.length).toBe(3);
expect(list.peekFront()).toBe(1);
expect(list.peekBack()).toBe(3);
});
});
expect(list.peekBack()).toBe(3);
expect(list.length).toBe(3);
});
describe('popBack', () => {
it('should return undefined for empty list', () => {
const list = new LinkedList<number>();
expect(list.popBack()).toBeUndefined();
});
describe('peekFront', () => {
it('should return undefined for empty list', () => {
const list = new LinkedList<number>();
it('should remove and return last value', () => {
const list = new LinkedList([1, 2, 3]);
expect(list.peekFront()).toBeUndefined();
});
it('should return first value without removing', () => {
const list = new LinkedList([1, 2, 3]);
expect(list.peekFront()).toBe(1);
expect(list.length).toBe(3);
});
expect(list.popBack()).toBe(3);
expect(list.length).toBe(2);
expect(list.peekBack()).toBe(2);
});
describe('insertBefore', () => {
it('should insert before head', () => {
const list = new LinkedList<number>();
const node = list.pushBack(2);
it('should handle single element', () => {
const list = new LinkedList(1);
list.insertBefore(node, 1);
expect(list.popBack()).toBe(1);
expect(list.isEmpty).toBe(true);
expect(list.head).toBeUndefined();
expect(list.tail).toBeUndefined();
});
});
expect(list.peekFront()).toBe(1);
expect(list.peekBack()).toBe(2);
expect(list.length).toBe(2);
});
describe('popFront', () => {
it('should return undefined for empty list', () => {
const list = new LinkedList<number>();
it('should insert before middle node', () => {
const list = new LinkedList([1, 3]);
const tail = list.tail!;
list.insertBefore(tail, 2);
expect(list.toArray()).toEqual([1, 2, 3]);
});
it('should return the created node', () => {
const list = new LinkedList<number>();
const existing = list.pushBack(2);
const newNode = list.insertBefore(existing, 1);
expect(newNode.value).toBe(1);
expect(newNode.next).toBe(existing);
});
expect(list.popFront()).toBeUndefined();
});
describe('insertAfter', () => {
it('should insert after tail', () => {
const list = new LinkedList<number>();
const node = list.pushBack(1);
it('should remove and return first value', () => {
const list = new LinkedList([1, 2, 3]);
list.insertAfter(node, 2);
expect(list.peekFront()).toBe(1);
expect(list.peekBack()).toBe(2);
expect(list.length).toBe(2);
});
it('should insert after middle node', () => {
const list = new LinkedList([1, 3]);
const head = list.head!;
list.insertAfter(head, 2);
expect(list.toArray()).toEqual([1, 2, 3]);
});
it('should return the created node', () => {
const list = new LinkedList<number>();
const existing = list.pushBack(1);
const newNode = list.insertAfter(existing, 2);
expect(newNode.value).toBe(2);
expect(newNode.prev).toBe(existing);
});
expect(list.popFront()).toBe(1);
expect(list.length).toBe(2);
expect(list.peekFront()).toBe(2);
});
describe('remove', () => {
it('should remove head node', () => {
const list = new LinkedList([1, 2, 3]);
const head = list.head!;
it('should handle single element', () => {
const list = new LinkedList(1);
const value = list.remove(head);
expect(list.popFront()).toBe(1);
expect(list.isEmpty).toBe(true);
expect(list.head).toBeUndefined();
expect(list.tail).toBeUndefined();
});
});
expect(value).toBe(1);
expect(list.length).toBe(2);
expect(list.peekFront()).toBe(2);
});
describe('peekBack', () => {
it('should return undefined for empty list', () => {
const list = new LinkedList<number>();
it('should remove tail node', () => {
const list = new LinkedList([1, 2, 3]);
const tail = list.tail!;
const value = list.remove(tail);
expect(value).toBe(3);
expect(list.length).toBe(2);
expect(list.peekBack()).toBe(2);
});
it('should remove middle node', () => {
const list = new LinkedList([1, 2, 3]);
const middle = list.head!.next!;
const value = list.remove(middle);
expect(value).toBe(2);
expect(list.toArray()).toEqual([1, 3]);
});
it('should remove single element', () => {
const list = new LinkedList<number>();
const node = list.pushBack(1);
list.remove(node);
expect(list.isEmpty).toBe(true);
expect(list.head).toBeUndefined();
expect(list.tail).toBeUndefined();
});
it('should detach the removed node', () => {
const list = new LinkedList([1, 2, 3]);
const middle = list.head!.next!;
list.remove(middle);
expect(middle.prev).toBeUndefined();
expect(middle.next).toBeUndefined();
});
expect(list.peekBack()).toBeUndefined();
});
describe('clear', () => {
it('should remove all elements', () => {
const list = new LinkedList([1, 2, 3]);
it('should return last value without removing', () => {
const list = new LinkedList([1, 2, 3]);
const result = list.clear();
expect(list.peekBack()).toBe(3);
expect(list.length).toBe(3);
});
});
expect(list.length).toBe(0);
expect(list.isEmpty).toBe(true);
expect(list.head).toBeUndefined();
expect(list.tail).toBeUndefined();
expect(result).toBe(list);
});
describe('peekFront', () => {
it('should return undefined for empty list', () => {
const list = new LinkedList<number>();
expect(list.peekFront()).toBeUndefined();
});
describe('toArray', () => {
it('should return empty array for empty list', () => {
const list = new LinkedList<number>();
it('should return first value without removing', () => {
const list = new LinkedList([1, 2, 3]);
expect(list.toArray()).toEqual([]);
});
expect(list.peekFront()).toBe(1);
expect(list.length).toBe(3);
});
});
it('should return values from head to tail', () => {
const list = new LinkedList([1, 2, 3]);
describe('insertBefore', () => {
it('should insert before head', () => {
const list = new LinkedList<number>();
const node = list.pushBack(2);
expect(list.toArray()).toEqual([1, 2, 3]);
});
list.insertBefore(node, 1);
expect(list.peekFront()).toBe(1);
expect(list.peekBack()).toBe(2);
expect(list.length).toBe(2);
});
describe('toString', () => {
it('should return comma-separated values', () => {
const list = new LinkedList([1, 2, 3]);
it('should insert before middle node', () => {
const list = new LinkedList([1, 3]);
const tail = list.tail!;
expect(list.toString()).toBe('1,2,3');
});
list.insertBefore(tail, 2);
expect(list.toArray()).toEqual([1, 2, 3]);
});
describe('iterator', () => {
it('should iterate from head to tail', () => {
const list = new LinkedList([1, 2, 3]);
it('should return the created node', () => {
const list = new LinkedList<number>();
const existing = list.pushBack(2);
expect([...list]).toEqual([1, 2, 3]);
});
const newNode = list.insertBefore(existing, 1);
it('should yield nothing for empty list', () => {
const list = new LinkedList<number>();
expect(newNode.value).toBe(1);
expect(newNode.next).toBe(existing);
});
});
expect([...list]).toEqual([]);
});
describe('insertAfter', () => {
it('should insert after tail', () => {
const list = new LinkedList<number>();
const node = list.pushBack(1);
list.insertAfter(node, 2);
expect(list.peekFront()).toBe(1);
expect(list.peekBack()).toBe(2);
expect(list.length).toBe(2);
});
describe('async iterator', () => {
it('should async iterate from head to tail', async () => {
const list = new LinkedList([1, 2, 3]);
const result: number[] = [];
it('should insert after middle node', () => {
const list = new LinkedList([1, 3]);
const head = list.head!;
for await (const value of list)
result.push(value);
list.insertAfter(head, 2);
expect(result).toEqual([1, 2, 3]);
});
expect(list.toArray()).toEqual([1, 2, 3]);
});
describe('node linking', () => {
it('should maintain correct prev/next references', () => {
const list = new LinkedList<number>();
const a = list.pushBack(1);
const b = list.pushBack(2);
const c = list.pushBack(3);
it('should return the created node', () => {
const list = new LinkedList<number>();
const existing = list.pushBack(1);
expect(a.next).toBe(b);
expect(b.prev).toBe(a);
expect(b.next).toBe(c);
expect(c.prev).toBe(b);
expect(a.prev).toBeUndefined();
expect(c.next).toBeUndefined();
});
const newNode = list.insertAfter(existing, 2);
it('should update links after removal', () => {
const list = new LinkedList<number>();
const a = list.pushBack(1);
const b = list.pushBack(2);
const c = list.pushBack(3);
expect(newNode.value).toBe(2);
expect(newNode.prev).toBe(existing);
});
});
list.remove(b);
describe('remove', () => {
it('should remove head node', () => {
const list = new LinkedList([1, 2, 3]);
const head = list.head!;
expect(a.next).toBe(c);
expect(c.prev).toBe(a);
});
const value = list.remove(head);
expect(value).toBe(1);
expect(list.length).toBe(2);
expect(list.peekFront()).toBe(2);
});
describe('interleaved operations', () => {
it('should handle mixed push/pop from both ends', () => {
const list = new LinkedList<number>();
it('should remove tail node', () => {
const list = new LinkedList([1, 2, 3]);
const tail = list.tail!;
list.pushBack(1);
list.pushBack(2);
list.pushFront(0);
const value = list.remove(tail);
expect(list.popFront()).toBe(0);
expect(list.popBack()).toBe(2);
expect(list.popFront()).toBe(1);
expect(list.isEmpty).toBe(true);
});
it('should handle insert and remove by node reference', () => {
const list = new LinkedList<number>();
const a = list.pushBack(1);
const c = list.pushBack(3);
const b = list.insertAfter(a, 2);
const d = list.insertBefore(c, 2.5);
expect(list.toArray()).toEqual([1, 2, 2.5, 3]);
list.remove(b);
list.remove(d);
expect(list.toArray()).toEqual([1, 3]);
});
expect(value).toBe(3);
expect(list.length).toBe(2);
expect(list.peekBack()).toBe(2);
});
it('should remove middle node', () => {
const list = new LinkedList([1, 2, 3]);
const middle = list.head!.next!;
const value = list.remove(middle);
expect(value).toBe(2);
expect(list.toArray()).toEqual([1, 3]);
});
it('should remove single element', () => {
const list = new LinkedList<number>();
const node = list.pushBack(1);
list.remove(node);
expect(list.isEmpty).toBe(true);
expect(list.head).toBeUndefined();
expect(list.tail).toBeUndefined();
});
it('should detach the removed node', () => {
const list = new LinkedList([1, 2, 3]);
const middle = list.head!.next!;
list.remove(middle);
expect(middle.prev).toBeUndefined();
expect(middle.next).toBeUndefined();
});
});
describe('clear', () => {
it('should remove all elements', () => {
const list = new LinkedList([1, 2, 3]);
const result = list.clear();
expect(list.length).toBe(0);
expect(list.isEmpty).toBe(true);
expect(list.head).toBeUndefined();
expect(list.tail).toBeUndefined();
expect(result).toBe(list);
});
});
describe('toArray', () => {
it('should return empty array for empty list', () => {
const list = new LinkedList<number>();
expect(list.toArray()).toEqual([]);
});
it('should return values from head to tail', () => {
const list = new LinkedList([1, 2, 3]);
expect(list.toArray()).toEqual([1, 2, 3]);
});
});
describe('toString', () => {
it('should return comma-separated values', () => {
const list = new LinkedList([1, 2, 3]);
expect(list.toString()).toBe('1,2,3');
});
});
describe('iterator', () => {
it('should iterate from head to tail', () => {
const list = new LinkedList([1, 2, 3]);
expect([...list]).toEqual([1, 2, 3]);
});
it('should yield nothing for empty list', () => {
const list = new LinkedList<number>();
expect([...list]).toEqual([]);
});
});
describe('async iterator', () => {
it('should async iterate from head to tail', async () => {
const list = new LinkedList([1, 2, 3]);
const result: number[] = [];
for await (const value of list)
result.push(value);
expect(result).toEqual([1, 2, 3]);
});
});
describe('node linking', () => {
it('should maintain correct prev/next references', () => {
const list = new LinkedList<number>();
const a = list.pushBack(1);
const b = list.pushBack(2);
const c = list.pushBack(3);
expect(a.next).toBe(b);
expect(b.prev).toBe(a);
expect(b.next).toBe(c);
expect(c.prev).toBe(b);
expect(a.prev).toBeUndefined();
expect(c.next).toBeUndefined();
});
it('should update links after removal', () => {
const list = new LinkedList<number>();
const a = list.pushBack(1);
const b = list.pushBack(2);
const c = list.pushBack(3);
list.remove(b);
expect(a.next).toBe(c);
expect(c.prev).toBe(a);
});
});
describe('interleaved operations', () => {
it('should handle mixed push/pop from both ends', () => {
const list = new LinkedList<number>();
list.pushBack(1);
list.pushBack(2);
list.pushFront(0);
expect(list.popFront()).toBe(0);
expect(list.popBack()).toBe(2);
expect(list.popFront()).toBe(1);
expect(list.isEmpty).toBe(true);
});
it('should handle insert and remove by node reference', () => {
const list = new LinkedList<number>();
const a = list.pushBack(1);
const c = list.pushBack(3);
const b = list.insertAfter(a, 2);
const d = list.insertBefore(c, 2.5);
expect(list.toArray()).toEqual([1, 2, 2.5, 3]);
list.remove(b);
list.remove(d);
expect(list.toArray()).toEqual([1, 3]);
});
});
});

View File

@@ -11,7 +11,7 @@ export type { LinkedListLike, LinkedListNode } from './types';
* @returns {LinkedListNode<T>} The created node
*/
function createNode<T>(value: T): LinkedListNode<T> {
return { value, prev: undefined, next: undefined };
return { value, prev: undefined, next: undefined };
}
/**
@@ -24,301 +24,307 @@ function createNode<T>(value: T): LinkedListNode<T> {
* @template T The type of elements stored in the list
*/
export class LinkedList<T> implements LinkedListLike<T> {
/**
/**
* The number of elements in the list
*
* @private
* @type {number}
*/
private count = 0;
private count = 0;
/**
/**
* The first node in the list
*
* @private
* @type {LinkedListNode<T> | undefined}
*/
private first: LinkedListNode<T> | undefined;
private first: LinkedListNode<T> | undefined;
/**
/**
* The last node in the list
*
* @private
* @type {LinkedListNode<T> | undefined}
*/
private last: LinkedListNode<T> | undefined;
private last: LinkedListNode<T> | undefined;
/**
/**
* Creates an instance of LinkedList
*
* @param {(T[] | T)} [initialValues] The initial values to add to the list
*/
constructor(initialValues?: T[] | T) {
if (initialValues !== null && initialValues !== undefined) {
const items = isArray(initialValues) ? initialValues : [initialValues];
constructor(initialValues?: T[] | T) {
if (initialValues !== null && initialValues !== undefined) {
const items = isArray(initialValues) ? initialValues : [initialValues];
for (const item of items)
this.pushBack(item);
}
for (const item of items)
this.pushBack(item);
}
}
/**
/**
* Gets the number of elements in the list
* @returns {number} The number of elements in the list
*/
public get length(): number {
return this.count;
}
public get length(): number {
return this.count;
}
/**
/**
* Checks if the list is empty
* @returns {boolean} `true` if the list is empty, `false` otherwise
*/
public get isEmpty(): boolean {
return this.count === 0;
}
public get isEmpty(): boolean {
return this.count === 0;
}
/**
/**
* Gets the first node
* @returns {LinkedListNode<T> | undefined} The first node, or `undefined` if the list is empty
*/
public get head(): LinkedListNode<T> | undefined {
return this.first;
}
public get head(): LinkedListNode<T> | undefined {
return this.first;
}
/**
/**
* Gets the last node
* @returns {LinkedListNode<T> | undefined} The last node, or `undefined` if the list is empty
*/
public get tail(): LinkedListNode<T> | undefined {
return this.last;
}
public get tail(): LinkedListNode<T> | undefined {
return this.last;
}
/**
/**
* Appends a value to the end of the list
* @param {T} value The value to append
* @returns {LinkedListNode<T>} The created node
*/
public pushBack(value: T): LinkedListNode<T> {
const node = createNode(value);
public pushBack(value: T): LinkedListNode<T> {
const node = createNode(value);
if (this.last) {
node.prev = this.last;
this.last.next = node;
this.last = node;
} else {
this.first = node;
this.last = node;
}
this.count++;
return node;
if (this.last) {
node.prev = this.last;
this.last.next = node;
this.last = node;
}
else {
this.first = node;
this.last = node;
}
/**
this.count++;
return node;
}
/**
* Prepends a value to the beginning of the list
* @param {T} value The value to prepend
* @returns {LinkedListNode<T>} The created node
*/
public pushFront(value: T): LinkedListNode<T> {
const node = createNode(value);
public pushFront(value: T): LinkedListNode<T> {
const node = createNode(value);
if (this.first) {
node.next = this.first;
this.first.prev = node;
this.first = node;
} else {
this.first = node;
this.last = node;
}
this.count++;
return node;
if (this.first) {
node.next = this.first;
this.first.prev = node;
this.first = node;
}
else {
this.first = node;
this.last = node;
}
/**
this.count++;
return node;
}
/**
* Removes and returns the last value
* @returns {T | undefined} The last value, or `undefined` if the list is empty
*/
public popBack(): T | undefined {
if (!this.last) return undefined;
public popBack(): T | undefined {
if (!this.last) return undefined;
const node = this.last;
const node = this.last;
this.detach(node);
this.detach(node);
return node.value;
}
return node.value;
}
/**
/**
* Removes and returns the first value
* @returns {T | undefined} The first value, or `undefined` if the list is empty
*/
public popFront(): T | undefined {
if (!this.first) return undefined;
public popFront(): T | undefined {
if (!this.first) return undefined;
const node = this.first;
const node = this.first;
this.detach(node);
this.detach(node);
return node.value;
}
return node.value;
}
/**
/**
* Returns the last value without removing it
* @returns {T | undefined} The last value, or `undefined` if the list is empty
*/
public peekBack(): T | undefined {
return this.last?.value;
}
public peekBack(): T | undefined {
return this.last?.value;
}
/**
/**
* Returns the first value without removing it
* @returns {T | undefined} The first value, or `undefined` if the list is empty
*/
public peekFront(): T | undefined {
return this.first?.value;
}
public peekFront(): T | undefined {
return this.first?.value;
}
/**
/**
* Inserts a value before the given node
* @param {LinkedListNode<T>} node The reference node
* @param {T} value The value to insert
* @returns {LinkedListNode<T>} The created node
*/
public insertBefore(node: LinkedListNode<T>, value: T): LinkedListNode<T> {
const newNode = createNode(value);
public insertBefore(node: LinkedListNode<T>, value: T): LinkedListNode<T> {
const newNode = createNode(value);
newNode.next = node;
newNode.prev = node.prev;
newNode.next = node;
newNode.prev = node.prev;
if (node.prev) {
node.prev.next = newNode;
} else {
this.first = newNode;
}
node.prev = newNode;
this.count++;
return newNode;
if (node.prev) {
node.prev.next = newNode;
}
else {
this.first = newNode;
}
/**
node.prev = newNode;
this.count++;
return newNode;
}
/**
* Inserts a value after the given node
* @param {LinkedListNode<T>} node The reference node
* @param {T} value The value to insert
* @returns {LinkedListNode<T>} The created node
*/
public insertAfter(node: LinkedListNode<T>, value: T): LinkedListNode<T> {
const newNode = createNode(value);
public insertAfter(node: LinkedListNode<T>, value: T): LinkedListNode<T> {
const newNode = createNode(value);
newNode.prev = node;
newNode.next = node.next;
newNode.prev = node;
newNode.next = node.next;
if (node.next) {
node.next.prev = newNode;
} else {
this.last = newNode;
}
node.next = newNode;
this.count++;
return newNode;
if (node.next) {
node.next.prev = newNode;
}
else {
this.last = newNode;
}
/**
node.next = newNode;
this.count++;
return newNode;
}
/**
* Removes a node from the list by reference in O(1)
* @param {LinkedListNode<T>} node The node to remove
* @returns {T} The value of the removed node
*/
public remove(node: LinkedListNode<T>): T {
this.detach(node);
public remove(node: LinkedListNode<T>): T {
this.detach(node);
return node.value;
}
return node.value;
}
/**
/**
* Removes all elements from the list
* @returns {this} The list instance for chaining
*/
public clear(): this {
this.first = undefined;
this.last = undefined;
this.count = 0;
public clear(): this {
this.first = undefined;
this.last = undefined;
this.count = 0;
return this;
}
return this;
}
/**
/**
* Returns a shallow copy of the list values as an array
* @returns {T[]} Array of values from head to tail
*/
public toArray(): T[] {
const result = Array.from<T>({ length: this.count });
let current = this.first;
let i = 0;
public toArray(): T[] {
const result = Array.from<T>({ length: this.count });
let current = this.first;
let i = 0;
while (current) {
result[i++] = current.value;
current = current.next;
}
return result;
while (current) {
result[i++] = current.value;
current = current.next;
}
/**
return result;
}
/**
* Returns a string representation of the list
* @returns {string} String representation
*/
public toString(): string {
return this.toArray().toString();
}
public toString(): string {
return this.toArray().toString();
}
/**
/**
* Iterator over list values from head to tail
*/
public *[Symbol.iterator](): Iterator<T> {
let current = this.first;
public* [Symbol.iterator](): Iterator<T> {
let current = this.first;
while (current) {
yield current.value;
current = current.next;
}
while (current) {
yield current.value;
current = current.next;
}
}
/**
/**
* Async iterator over list values from head to tail
*/
public async *[Symbol.asyncIterator](): AsyncIterator<T> {
for (const value of this)
yield value;
}
public async* [Symbol.asyncIterator](): AsyncIterator<T> {
for (const value of this)
yield value;
}
/**
/**
* Detaches a node from the list, updating head/tail and count
*
* @private
* @param {LinkedListNode<T>} node The node to detach
*/
private detach(node: LinkedListNode<T>): void {
if (node.prev) {
node.prev.next = node.next;
} else {
this.first = node.next;
}
if (node.next) {
node.next.prev = node.prev;
} else {
this.last = node.prev;
}
node.prev = undefined;
node.next = undefined;
this.count--;
private detach(node: LinkedListNode<T>): void {
if (node.prev) {
node.prev.next = node.next;
}
else {
this.first = node.next;
}
if (node.next) {
node.next.prev = node.prev;
}
else {
this.last = node.prev;
}
node.prev = undefined;
node.next = undefined;
this.count--;
}
}

View File

@@ -1,28 +1,28 @@
export interface LinkedListNode<T> {
value: T;
prev: LinkedListNode<T> | undefined;
next: LinkedListNode<T> | undefined;
value: T;
prev: LinkedListNode<T> | undefined;
next: LinkedListNode<T> | undefined;
}
export interface LinkedListLike<T> extends Iterable<T>, AsyncIterable<T> {
readonly length: number;
readonly isEmpty: boolean;
readonly length: number;
readonly isEmpty: boolean;
readonly head: LinkedListNode<T> | undefined;
readonly tail: LinkedListNode<T> | undefined;
readonly head: LinkedListNode<T> | undefined;
readonly tail: LinkedListNode<T> | undefined;
pushBack(value: T): LinkedListNode<T>;
pushFront(value: T): LinkedListNode<T>;
popBack(): T | undefined;
popFront(): T | undefined;
peekBack(): T | undefined;
peekFront(): T | undefined;
pushBack(value: T): LinkedListNode<T>;
pushFront(value: T): LinkedListNode<T>;
popBack(): T | undefined;
popFront(): T | undefined;
peekBack(): T | undefined;
peekFront(): T | undefined;
insertBefore(node: LinkedListNode<T>, value: T): LinkedListNode<T>;
insertAfter(node: LinkedListNode<T>, value: T): LinkedListNode<T>;
remove(node: LinkedListNode<T>): T;
insertBefore(node: LinkedListNode<T>, value: T): LinkedListNode<T>;
insertAfter(node: LinkedListNode<T>, value: T): LinkedListNode<T>;
remove(node: LinkedListNode<T>): T;
clear(): this;
toArray(): T[];
toString(): string;
clear(): this;
toArray(): T[];
toString(): string;
}

View File

@@ -3,211 +3,211 @@ import { describe, expect, it } from 'vitest';
import { PriorityQueue } from '.';
describe('PriorityQueue', () => {
describe('constructor', () => {
it('should create an empty queue', () => {
const pq = new PriorityQueue<number>();
describe('constructor', () => {
it('should create an empty queue', () => {
const pq = new PriorityQueue<number>();
expect(pq.length).toBe(0);
expect(pq.isEmpty).toBe(true);
expect(pq.isFull).toBe(false);
});
it('should create a queue from single value', () => {
const pq = new PriorityQueue(42);
expect(pq.length).toBe(1);
expect(pq.peek()).toBe(42);
});
it('should create a queue from array', () => {
const pq = new PriorityQueue([5, 3, 8, 1, 4]);
expect(pq.length).toBe(5);
expect(pq.peek()).toBe(1);
});
it('should throw if initial values exceed maxSize', () => {
expect(() => new PriorityQueue([1, 2, 3], { maxSize: 2 }))
.toThrow('Initial values exceed maxSize');
});
expect(pq.length).toBe(0);
expect(pq.isEmpty).toBe(true);
expect(pq.isFull).toBe(false);
});
describe('enqueue', () => {
it('should enqueue elements by priority', () => {
const pq = new PriorityQueue<number>();
it('should create a queue from single value', () => {
const pq = new PriorityQueue(42);
pq.enqueue(5);
pq.enqueue(1);
pq.enqueue(3);
expect(pq.peek()).toBe(1);
expect(pq.length).toBe(3);
});
it('should throw when queue is full', () => {
const pq = new PriorityQueue<number>(undefined, { maxSize: 2 });
pq.enqueue(1);
pq.enqueue(2);
expect(() => pq.enqueue(3)).toThrow('PriorityQueue is full');
});
expect(pq.length).toBe(1);
expect(pq.peek()).toBe(42);
});
describe('dequeue', () => {
it('should return undefined for empty queue', () => {
const pq = new PriorityQueue<number>();
it('should create a queue from array', () => {
const pq = new PriorityQueue([5, 3, 8, 1, 4]);
expect(pq.dequeue()).toBeUndefined();
});
it('should dequeue elements in priority order (min-heap)', () => {
const pq = new PriorityQueue([5, 3, 8, 1, 4]);
const result: number[] = [];
while (!pq.isEmpty) {
result.push(pq.dequeue()!);
}
expect(result).toEqual([1, 3, 4, 5, 8]);
});
it('should dequeue elements in priority order (max-heap)', () => {
const pq = new PriorityQueue([5, 3, 8, 1, 4], {
comparator: (a, b) => b - a,
});
const result: number[] = [];
while (!pq.isEmpty) {
result.push(pq.dequeue()!);
}
expect(result).toEqual([8, 5, 4, 3, 1]);
});
expect(pq.length).toBe(5);
expect(pq.peek()).toBe(1);
});
describe('peek', () => {
it('should return undefined for empty queue', () => {
const pq = new PriorityQueue<number>();
it('should throw if initial values exceed maxSize', () => {
expect(() => new PriorityQueue([1, 2, 3], { maxSize: 2 }))
.toThrow('Initial values exceed maxSize');
});
});
expect(pq.peek()).toBeUndefined();
});
describe('enqueue', () => {
it('should enqueue elements by priority', () => {
const pq = new PriorityQueue<number>();
it('should return highest-priority element without removing', () => {
const pq = new PriorityQueue([5, 1, 3]);
pq.enqueue(5);
pq.enqueue(1);
pq.enqueue(3);
expect(pq.peek()).toBe(1);
expect(pq.length).toBe(3);
});
expect(pq.peek()).toBe(1);
expect(pq.length).toBe(3);
});
describe('isFull', () => {
it('should be false when no maxSize', () => {
const pq = new PriorityQueue([1, 2, 3]);
it('should throw when queue is full', () => {
const pq = new PriorityQueue<number>(undefined, { maxSize: 2 });
expect(pq.isFull).toBe(false);
});
pq.enqueue(1);
pq.enqueue(2);
it('should be true when at maxSize', () => {
const pq = new PriorityQueue([1, 2], { maxSize: 2 });
expect(() => pq.enqueue(3)).toThrow('PriorityQueue is full');
});
});
expect(pq.isFull).toBe(true);
});
describe('dequeue', () => {
it('should return undefined for empty queue', () => {
const pq = new PriorityQueue<number>();
it('should become false after dequeue', () => {
const pq = new PriorityQueue([1, 2], { maxSize: 2 });
pq.dequeue();
expect(pq.isFull).toBe(false);
});
expect(pq.dequeue()).toBeUndefined();
});
describe('clear', () => {
it('should remove all elements', () => {
const pq = new PriorityQueue([1, 2, 3]);
it('should dequeue elements in priority order (min-heap)', () => {
const pq = new PriorityQueue([5, 3, 8, 1, 4]);
const result: number[] = [];
const result = pq.clear();
while (!pq.isEmpty) {
result.push(pq.dequeue()!);
}
expect(pq.length).toBe(0);
expect(pq.isEmpty).toBe(true);
expect(result).toBe(pq);
});
expect(result).toEqual([1, 3, 4, 5, 8]);
});
describe('toArray', () => {
it('should return empty array for empty queue', () => {
const pq = new PriorityQueue<number>();
it('should dequeue elements in priority order (max-heap)', () => {
const pq = new PriorityQueue([5, 3, 8, 1, 4], {
comparator: (a, b) => b - a,
});
const result: number[] = [];
expect(pq.toArray()).toEqual([]);
});
while (!pq.isEmpty) {
result.push(pq.dequeue()!);
}
it('should return a shallow copy', () => {
const pq = new PriorityQueue([3, 1, 2]);
const arr = pq.toArray();
expect(result).toEqual([8, 5, 4, 3, 1]);
});
});
arr.push(99);
describe('peek', () => {
it('should return undefined for empty queue', () => {
const pq = new PriorityQueue<number>();
expect(pq.length).toBe(3);
});
expect(pq.peek()).toBeUndefined();
});
describe('toString', () => {
it('should return formatted string', () => {
const pq = new PriorityQueue([1, 2, 3]);
it('should return highest-priority element without removing', () => {
const pq = new PriorityQueue([5, 1, 3]);
expect(pq.toString()).toBe('PriorityQueue(3)');
});
expect(pq.peek()).toBe(1);
expect(pq.length).toBe(3);
});
});
describe('isFull', () => {
it('should be false when no maxSize', () => {
const pq = new PriorityQueue([1, 2, 3]);
expect(pq.isFull).toBe(false);
});
describe('iterator', () => {
it('should iterate over elements', () => {
const pq = new PriorityQueue([5, 3, 1]);
const elements = [...pq];
it('should be true when at maxSize', () => {
const pq = new PriorityQueue([1, 2], { maxSize: 2 });
expect(elements.length).toBe(3);
});
expect(pq.isFull).toBe(true);
});
describe('custom comparator', () => {
it('should work with object priority', () => {
interface Job {
priority: number;
name: string;
}
it('should become false after dequeue', () => {
const pq = new PriorityQueue([1, 2], { maxSize: 2 });
const pq = new PriorityQueue<Job>(
[
{ priority: 3, name: 'low' },
{ priority: 1, name: 'critical' },
{ priority: 2, name: 'normal' },
],
{ comparator: (a, b) => a.priority - b.priority },
);
pq.dequeue();
expect(pq.dequeue()?.name).toBe('critical');
expect(pq.dequeue()?.name).toBe('normal');
expect(pq.dequeue()?.name).toBe('low');
});
expect(pq.isFull).toBe(false);
});
});
describe('clear', () => {
it('should remove all elements', () => {
const pq = new PriorityQueue([1, 2, 3]);
const result = pq.clear();
expect(pq.length).toBe(0);
expect(pq.isEmpty).toBe(true);
expect(result).toBe(pq);
});
});
describe('toArray', () => {
it('should return empty array for empty queue', () => {
const pq = new PriorityQueue<number>();
expect(pq.toArray()).toEqual([]);
});
describe('interleaved operations', () => {
it('should maintain priority with mixed enqueue and dequeue', () => {
const pq = new PriorityQueue<number>();
it('should return a shallow copy', () => {
const pq = new PriorityQueue([3, 1, 2]);
const arr = pq.toArray();
pq.enqueue(10);
pq.enqueue(5);
expect(pq.dequeue()).toBe(5);
arr.push(99);
pq.enqueue(3);
pq.enqueue(7);
expect(pq.dequeue()).toBe(3);
pq.enqueue(1);
expect(pq.dequeue()).toBe(1);
expect(pq.dequeue()).toBe(7);
expect(pq.dequeue()).toBe(10);
expect(pq.dequeue()).toBeUndefined();
});
expect(pq.length).toBe(3);
});
});
describe('toString', () => {
it('should return formatted string', () => {
const pq = new PriorityQueue([1, 2, 3]);
expect(pq.toString()).toBe('PriorityQueue(3)');
});
});
describe('iterator', () => {
it('should iterate over elements', () => {
const pq = new PriorityQueue([5, 3, 1]);
const elements = [...pq];
expect(elements.length).toBe(3);
});
});
describe('custom comparator', () => {
it('should work with object priority', () => {
interface Job {
priority: number;
name: string;
}
const pq = new PriorityQueue<Job>(
[
{ priority: 3, name: 'low' },
{ priority: 1, name: 'critical' },
{ priority: 2, name: 'normal' },
],
{ comparator: (a, b) => a.priority - b.priority },
);
expect(pq.dequeue()?.name).toBe('critical');
expect(pq.dequeue()?.name).toBe('normal');
expect(pq.dequeue()?.name).toBe('low');
});
});
describe('interleaved operations', () => {
it('should maintain priority with mixed enqueue and dequeue', () => {
const pq = new PriorityQueue<number>();
pq.enqueue(10);
pq.enqueue(5);
expect(pq.dequeue()).toBe(5);
pq.enqueue(3);
pq.enqueue(7);
expect(pq.dequeue()).toBe(3);
pq.enqueue(1);
expect(pq.dequeue()).toBe(1);
expect(pq.dequeue()).toBe(7);
expect(pq.dequeue()).toBe(10);
expect(pq.dequeue()).toBeUndefined();
});
});
});

View File

@@ -5,8 +5,8 @@ export type { PriorityQueueLike } from './types';
export type { Comparator } from './types';
export interface PriorityQueueOptions<T> {
comparator?: Comparator<T>;
maxSize?: number;
comparator?: Comparator<T>;
maxSize?: number;
}
/**
@@ -19,126 +19,126 @@ export interface PriorityQueueOptions<T> {
* @template T The type of elements stored in the queue
*/
export class PriorityQueue<T> implements PriorityQueueLike<T> {
/**
/**
* The maximum number of elements the queue can hold
*
* @private
* @type {number}
*/
private readonly maxSize: number;
private readonly maxSize: number;
/**
/**
* Internal binary heap backing the queue
*
* @private
* @type {BinaryHeap<T>}
*/
private readonly heap: BinaryHeap<T>;
private readonly heap: BinaryHeap<T>;
/**
/**
* Creates an instance of PriorityQueue
*
* @param {(T[] | T)} [initialValues] The initial values to add to the queue
* @param {PriorityQueueOptions<T>} [options] Queue configuration
*/
constructor(initialValues?: T[] | T, options?: PriorityQueueOptions<T>) {
this.maxSize = options?.maxSize ?? Infinity;
this.heap = new BinaryHeap(initialValues, { comparator: options?.comparator });
constructor(initialValues?: T[] | T, options?: PriorityQueueOptions<T>) {
this.maxSize = options?.maxSize ?? Infinity;
this.heap = new BinaryHeap(initialValues, { comparator: options?.comparator });
if (this.heap.length > this.maxSize) {
throw new RangeError('Initial values exceed maxSize');
}
if (this.heap.length > this.maxSize) {
throw new RangeError('Initial values exceed maxSize');
}
}
/**
/**
* Gets the number of elements in the queue
* @returns {number} The number of elements in the queue
*/
public get length(): number {
return this.heap.length;
}
public get length(): number {
return this.heap.length;
}
/**
/**
* Checks if the queue is empty
* @returns {boolean} `true` if the queue is empty, `false` otherwise
*/
public get isEmpty(): boolean {
return this.heap.isEmpty;
}
public get isEmpty(): boolean {
return this.heap.isEmpty;
}
/**
/**
* Checks if the queue is full
* @returns {boolean} `true` if the queue has reached maxSize, `false` otherwise
*/
public get isFull(): boolean {
return this.heap.length >= this.maxSize;
}
public get isFull(): boolean {
return this.heap.length >= this.maxSize;
}
/**
/**
* Enqueues an element by priority
* @param {T} element The element to enqueue
* @throws {RangeError} If the queue is full
*/
public enqueue(element: T): void {
if (this.isFull)
throw new RangeError('PriorityQueue is full');
public enqueue(element: T): void {
if (this.isFull)
throw new RangeError('PriorityQueue is full');
this.heap.push(element);
}
this.heap.push(element);
}
/**
/**
* Dequeues the highest-priority element
* @returns {T | undefined} The highest-priority element, or `undefined` if empty
*/
public dequeue(): T | undefined {
return this.heap.pop();
}
public dequeue(): T | undefined {
return this.heap.pop();
}
/**
/**
* Returns the highest-priority element without removing it
* @returns {T | undefined} The highest-priority element, or `undefined` if empty
*/
public peek(): T | undefined {
return this.heap.peek();
}
public peek(): T | undefined {
return this.heap.peek();
}
/**
/**
* Removes all elements from the queue
* @returns {this} The queue instance for chaining
*/
public clear(): this {
this.heap.clear();
return this;
}
public clear(): this {
this.heap.clear();
return this;
}
/**
/**
* Returns a shallow copy of elements in heap order
* @returns {T[]} Array of elements
*/
public toArray(): T[] {
return this.heap.toArray();
}
public toArray(): T[] {
return this.heap.toArray();
}
/**
/**
* Returns a string representation of the queue
* @returns {string} String representation
*/
public toString(): string {
return `PriorityQueue(${this.heap.length})`;
}
public toString(): string {
return `PriorityQueue(${this.heap.length})`;
}
/**
/**
* Iterator over queue elements in heap order
*/
public *[Symbol.iterator](): Iterator<T> {
yield* this.heap;
}
public* [Symbol.iterator](): Iterator<T> {
yield* this.heap;
}
/**
/**
* Async iterator over queue elements in heap order
*/
public async *[Symbol.asyncIterator](): AsyncIterator<T> {
for (const element of this.heap)
yield element;
}
public async* [Symbol.asyncIterator](): AsyncIterator<T> {
for (const element of this.heap)
yield element;
}
}

View File

@@ -1,16 +1,16 @@
import type { Comparator } from '../BinaryHeap';
export interface PriorityQueueLike<T> extends Iterable<T>, AsyncIterable<T> {
readonly length: number;
readonly isEmpty: boolean;
readonly isFull: boolean;
readonly length: number;
readonly isEmpty: boolean;
readonly isFull: boolean;
enqueue(element: T): void;
dequeue(): T | undefined;
peek(): T | undefined;
clear(): this;
toArray(): T[];
toString(): string;
enqueue(element: T): void;
dequeue(): T | undefined;
peek(): T | undefined;
clear(): this;
toArray(): T[];
toString(): string;
}
export type { Comparator };

View File

@@ -133,7 +133,7 @@ export class Queue<T> implements QueueLike<T> {
*
* @returns {AsyncIterableIterator<T>}
*/
async *[Symbol.asyncIterator]() {
async* [Symbol.asyncIterator]() {
for (const element of this.deque)
yield element;
}

View File

@@ -112,8 +112,8 @@ describe('stack', () => {
for await (const element of stack) {
elements.push(element);
}
expect(elements).toEqual([3, 2, 1]);
});
});
});
});

View File

@@ -5,7 +5,7 @@ import type { StackLike } from './types';
export type { StackLike } from './types';
export interface StackOptions {
maxSize?: number;
maxSize?: number;
}
/**
@@ -18,138 +18,138 @@ export interface StackOptions {
* @template T The type of elements stored in the stack
*/
export class Stack<T> implements StackLike<T> {
/**
/**
* The maximum number of elements that the stack can hold
*
*
* @private
* @type {number}
*/
private readonly maxSize: number;
private readonly maxSize: number;
/**
/**
* The stack data structure
*
*
* @private
* @type {T[]}
*/
private readonly stack: T[];
private readonly stack: T[];
/**
/**
* Creates an instance of Stack
*
*
* @param {(T[] | T)} [initialValues] The initial values to add to the stack
* @param {StackOptions} [options] The options for the stack
* @memberof Stack
*/
constructor(initialValues?: T[] | T, options?: StackOptions) {
this.maxSize = options?.maxSize ?? Infinity;
this.stack = isArray(initialValues) ? initialValues : initialValues ? [initialValues] : [];
}
/**
constructor(initialValues?: T[] | T, options?: StackOptions) {
this.maxSize = options?.maxSize ?? Infinity;
this.stack = isArray(initialValues) ? initialValues : initialValues ? [initialValues] : [];
}
/**
* Gets the number of elements in the stack
* @returns {number} The number of elements in the stack
*/
public get length() {
return this.stack.length;
}
public get length() {
return this.stack.length;
}
/**
/**
* Checks if the stack is empty
* @returns {boolean} `true` if the stack is empty, `false` otherwise
*/
public get isEmpty() {
return this.stack.length === 0;
}
public get isEmpty() {
return this.stack.length === 0;
}
/**
/**
* Checks if the stack is full
* @returns {boolean} `true` if the stack is full, `false` otherwise
*/
public get isFull() {
return this.stack.length === this.maxSize;
}
public get isFull() {
return this.stack.length === this.maxSize;
}
/**
/**
* Pushes an element onto the stack
* @param {T} element The element to push onto the stack
* @returns {this}
* @throws {RangeError} If the stack is full
*/
public push(element: T) {
if (this.isFull)
throw new RangeError('Stack is full');
this.stack.push(element);
public push(element: T) {
if (this.isFull)
throw new RangeError('Stack is full');
return this;
}
this.stack.push(element);
/**
return this;
}
/**
* Pops an element from the stack
* @returns {T | undefined} The element popped from the stack
*/
public pop() {
return this.stack.pop();
}
public pop() {
return this.stack.pop();
}
/**
/**
* Peeks at the top element of the stack
* @returns {T | undefined} The top element of the stack
*/
public peek() {
if (this.isEmpty)
return undefined;
return last(this.stack);
}
public peek() {
if (this.isEmpty)
return undefined;
/**
return last(this.stack);
}
/**
* Clears the stack
*
*
* @returns {this}
*/
public clear() {
this.stack.length = 0;
public clear() {
this.stack.length = 0;
return this;
}
return this;
}
/**
/**
* Converts the stack to an array
*
*
* @returns {T[]}
*/
public toArray() {
return this.stack.toReversed();
}
public toArray() {
return this.stack.toReversed();
}
/**
/**
* Returns a string representation of the stack
*
*
* @returns {string}
*/
public toString() {
return this.toArray().toString();
}
public toString() {
return this.toArray().toString();
}
/**
/**
* Returns an iterator for the stack
*
*
* @returns {IterableIterator<T>}
*/
public [Symbol.iterator]() {
return this.toArray()[Symbol.iterator]();
}
public [Symbol.iterator]() {
return this.toArray()[Symbol.iterator]();
}
/**
/**
* Returns an async iterator for the stack
*
*
* @returns {AsyncIterableIterator<T>}
*/
public async *[Symbol.asyncIterator]() {
for (const element of this.toArray()) {
yield element;
}
public async* [Symbol.asyncIterator]() {
for (const element of this.toArray()) {
yield element;
}
}
}

View File

@@ -4,4 +4,4 @@ export * from './Deque';
export * from './LinkedList';
export * from './PriorityQueue';
export * from './Queue';
export * from './Stack';
export * from './Stack';