UNPKG

ts-data-forge

Version:

[![npm version](https://img.shields.io/npm/v/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![npm downloads](https://img.shields.io/npm/dm/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![License](https://img.shields.

393 lines (367 loc) 13 kB
import { Optional } from '../functional/index.mjs'; import { range } from '../iterator/index.mjs'; import { asSafeUint, asUint32 } from '../number/index.mjs'; /** * Interface for a high-performance queue with FIFO (First-In, First-Out) behavior. * * This interface defines a mutable queue data structure where elements are added to the back * and removed from the front, maintaining the order in which they were added. The implementation * uses a circular buffer for optimal performance, providing O(1) operations for both enqueue * and dequeue operations. * * **FIFO Behavior:** * - **enqueue**: Adds elements to the back of the queue * - **dequeue**: Removes and returns elements from the front of the queue * - Elements are processed in the exact order they were added * * **Performance Characteristics:** * - enqueue: O(1) amortized (O(n) when buffer needs resizing) * - dequeue: O(1) always * - size/isEmpty: O(1) always * - Memory efficient with automatic garbage collection of removed elements * * **Use Cases:** * - Task scheduling and job queues * - Breadth-first search algorithms * - Event processing systems * - Producer-consumer patterns * - Buffer management for streaming data * * @template T The type of elements stored in the queue. * * @example * ```typescript * import { createQueue, Queue } from './queue'; * * // Example 1: Basic FIFO operations * const messageQueue: Queue<string> = createQueue<string>(); * * messageQueue.enqueue("first message"); // Add to back * messageQueue.enqueue("second message"); // Add to back * messageQueue.enqueue("third message"); // Add to back * * console.log(messageQueue.size); // Output: 3 * * // Process messages in FIFO order * console.log(messageQueue.dequeue().unwrap()); // "first message" (first in, first out) * console.log(messageQueue.dequeue().unwrap()); // "second message" * console.log(messageQueue.size); // Output: 1 * * // Example 2: Task processing system * type Task = { id: number; priority: string; action: () => void }; * const taskQueue: Queue<Task> = createQueue<Task>(); * * taskQueue.enqueue({ id: 1, priority: "high", action: () => console.log("Task 1") }); * taskQueue.enqueue({ id: 2, priority: "low", action: () => console.log("Task 2") }); * * // Process tasks in order * while (!taskQueue.isEmpty) { * const task = taskQueue.dequeue().unwrap(); * console.log(`Processing task ${task.id} with ${task.priority} priority`); * task.action(); * } * ``` */ export type Queue<T> = Readonly<{ /** Checks if the queue is empty. */ isEmpty: boolean; /** The number of elements in the queue. */ size: SizeType.Arr; /** * Removes and returns the element at the front of the queue. * @returns The element at the front of the queue, or `Optional.none` if the queue is empty. */ dequeue: () => Optional<T>; /** * Adds an element to the back of the queue. * @param value The element to add. */ enqueue: (value: T) => void; }>; /** * Class implementation for a queue with FIFO (First-In, First-Out) behavior using a circular buffer. * This implementation provides O(1) enqueue and dequeue operations by using a fixed-size buffer * with head and tail pointers that wrap around when they reach the buffer boundary. * * The circular buffer automatically resizes when it becomes full, ensuring that the queue * can grow to accommodate any number of elements while maintaining efficient operations. * * @template T The type of elements in the queue. * @implements Queue */ class QueueClass<T> implements Queue<T> { /** @internal Circular buffer to store queue elements. */ #buffer: (T | undefined)[]; /** @internal Index of the first element (front of queue). */ #head: number; /** @internal Index where the next element will be added (back of queue). */ #tail: number; /** @internal Current number of elements in the queue. */ #mut_size: number; /** @internal Current capacity of the buffer. */ #capacity: number; /** @internal Initial capacity for new queues. */ static readonly #INITIAL_CAPACITY = 8; /** * Constructs a new QueueClass instance. * @param initialValues Optional initial values to populate the queue. */ constructor(initialValues: readonly T[] = []) { const initialCapacity = asUint32( Math.max(QueueClass.#INITIAL_CAPACITY, initialValues.length * 2), ); this.#buffer = Array.from<unknown, T | undefined>( { length: initialCapacity }, () => undefined, ); this.#head = 0; this.#tail = 0; this.#mut_size = 0; this.#capacity = initialCapacity; // Add initial values for (const value of initialValues) { this.enqueue(value); } } /** @inheritdoc */ get isEmpty(): boolean { return this.#mut_size === 0; } /** @inheritdoc */ get size(): SizeType.Arr { return asUint32(this.#mut_size); } /** * Removes and returns the element at the front of the queue (FIFO). * * This operation removes the element that was added earliest (first-in) and returns it. * If the queue is empty, returns `Optional.none`. The operation is guaranteed to be O(1) * and does not require any array shifting or copying. * * **Time Complexity:** O(1) - constant time operation * **Space Complexity:** O(1) - no additional memory allocation * * @returns An Optional containing the removed element, or `Optional.none` if the queue is empty. * * @example * ```typescript * const queue = createQueue<string>(); * * // Add some elements * queue.enqueue("first"); * queue.enqueue("second"); * queue.enqueue("third"); * * // Remove elements in FIFO order * const first = queue.dequeue(); * if (first.isSome) { * console.log(first.value); // "first" * } * * const second = queue.dequeue().unwrap(); // "second" * console.log(queue.size); // 1 * * // Safe handling of empty queue * const emptyQueue = createQueue<number>(); * const result = emptyQueue.dequeue(); * if (result.isNone) { * console.log("Queue is empty"); * } * ``` */ dequeue(): Optional<T> { if (this.isEmpty) { return Optional.none; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const element = this.#buffer[this.#head]!; this.#buffer[this.#head] = undefined; // Clear reference for garbage collection this.#head = (this.#head + 1) % this.#capacity; this.#mut_size -= 1; return Optional.some(element); } /** * Adds an element to the back of the queue (FIFO). * * This operation adds the element to the end of the queue, where it will be the last * to be dequeued (first-in, first-out ordering). The operation is amortized O(1), * meaning it's O(1) for most operations with occasional O(n) when the buffer needs resizing. * * **Time Complexity:** O(1) amortized - O(n) only when buffer resize is needed * **Space Complexity:** O(1) - constant additional memory per element * * **Buffer Resizing:** When the internal buffer becomes full, it automatically doubles * in size and reorganizes elements to maintain the circular buffer structure. * * @param value The element to add to the back of the queue. * * @example * ```typescript * const taskQueue = createQueue<string>(); * * // Add tasks in order of arrival * taskQueue.enqueue("Process order #1001"); // O(1) * taskQueue.enqueue("Send notification"); // O(1) * taskQueue.enqueue("Update inventory"); // O(1) * * console.log(taskQueue.size); // 3 * * // Tasks will be processed in the order they were added * while (!taskQueue.isEmpty) { * const task = taskQueue.dequeue().unwrap(); * console.log(`Executing: ${task}`); * } * * // High-volume enqueueing (demonstrates amortized O(1) performance) * const dataQueue = createQueue<number>(); * * for (const i of range(1000000)) { * dataQueue.enqueue(i); // Each operation is O(1) amortized * } * * console.log(dataQueue.size); // 1000000 * ``` */ enqueue(value: T): void { // Resize if buffer is full if (this.#mut_size === this.#capacity) { this.#resize(); } this.#buffer[this.#tail] = value; this.#tail = (this.#tail + 1) % this.#capacity; this.#mut_size += 1; } /** * @internal * Resizes the circular buffer when it becomes full. * Doubles the capacity and reorganizes elements to maintain queue order. */ #resize(): void { const newCapacity = asUint32(this.#capacity * 2); const newBuffer = Array.from<unknown, T | undefined>( { length: newCapacity }, () => undefined, ); // Copy elements in order from head to tail for (const i of range(asSafeUint(this.#mut_size))) { const sourceIndex = (this.#head + i) % this.#capacity; newBuffer[i] = this.#buffer[sourceIndex]; } this.#buffer = newBuffer; this.#head = 0; this.#tail = this.#mut_size; this.#capacity = newCapacity; } } /** * Creates a new Queue instance with FIFO (First-In, First-Out) behavior using a high-performance circular buffer. * * This factory function creates an optimized queue implementation that maintains excellent performance * characteristics for both enqueue and dequeue operations. The underlying circular buffer automatically * resizes to accommodate growing workloads while providing predictable O(1) operations. * * **Implementation Features:** * - **O(1) enqueue operations** (amortized - occasionally O(n) when resizing) * - **O(1) dequeue operations** (always) * - **Automatic buffer resizing** - starts at 8 elements, doubles when full * - **Memory efficient** - garbage collects removed elements immediately * - **Circular buffer design** - eliminates need for array shifting operations * * **Performance Benefits:** * - No array copying during normal operations * - Minimal memory allocation overhead * - Predictable performance under high load * - Efficient memory usage with automatic cleanup * * @template T The type of elements stored in the queue. * @param initialValues Optional array of initial elements to populate the queue. * Elements will be dequeued in the same order they appear in the array. * If provided, the initial buffer capacity will be at least twice the array length. * @returns A new Queue instance optimized for high-performance FIFO operations. * * @example * ```typescript * import { createQueue } from './queue'; * * // Example 1: Basic FIFO workflow * const requestQueue = createQueue<string>(); * * // Add requests to the queue * requestQueue.enqueue("GET /api/users"); // O(1) * requestQueue.enqueue("POST /api/orders"); // O(1) * requestQueue.enqueue("DELETE /api/cache"); // O(1) * * // Process requests in order * while (!requestQueue.isEmpty) { * const request = requestQueue.dequeue().unwrap(); // O(1) * console.log(`Processing: ${request}`); * } * // Output: * // Processing: GET /api/users * // Processing: POST /api/orders * // Processing: DELETE /api/cache * * // Example 2: High-throughput event processing * type Event = { timestamp: number; type: string; data: any }; * const eventQueue = createQueue<Event>(); * * // Simulate high-volume event ingestion * for (const i of range(10000)) { * eventQueue.enqueue({ * timestamp: Date.now(), * type: `event-${i % 5}`, * data: { value: i } * }); // Each enqueue is O(1) amortized * } * * // Process events efficiently * let processedCount = 0; * while (!eventQueue.isEmpty) { * const event = eventQueue.dequeue().unwrap(); // O(1) * // Process event... * processedCount++; * } * console.log(`Processed ${processedCount} events`); // 10000 * * // Example 3: Queue with pre-populated data * const priorityTasks = createQueue<string>([ * "Initialize system", * "Load configuration", * "Start services", * "Begin processing" * ]); * * console.log(priorityTasks.size); // Output: 4 * * // Execute tasks in initialization order * while (!priorityTasks.isEmpty) { * const task = priorityTasks.dequeue().unwrap(); * console.log(`Executing: ${task}`); * } * * // Example 4: Producer-Consumer pattern * const workQueue = createQueue<() => Promise<void>>(); * * // Producer: Add work items * const addWork = (workFn: () => Promise<void>) => { * workQueue.enqueue(workFn); * }; * * // Consumer: Process work items * const processWork = async () => { * while (!workQueue.isEmpty) { * const workItem = workQueue.dequeue().unwrap(); * await workItem(); * } * }; * * // Add some work * addWork(async () => console.log("Work item 1")); * addWork(async () => console.log("Work item 2")); * * // Process the work * await processWork(); * ``` */ export const createQueue = <T,>(initialValues?: readonly T[]): Queue<T> => new QueueClass<T>(initialValues);