From 7546f2b653be14b90bce6287a9156ced6e045490 Mon Sep 17 00:00:00 2001 From: robonen Date: Wed, 17 Apr 2024 04:00:41 +0700 Subject: [PATCH] feat(packages/stdlib): add stack data structure and tests --- packages/stdlib/src/structs/index.ts | 1 + .../stdlib/src/structs/stack/index.test.ts | 117 ++++++++++++++ packages/stdlib/src/structs/stack/index.ts | 144 ++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 packages/stdlib/src/structs/index.ts create mode 100644 packages/stdlib/src/structs/stack/index.test.ts create mode 100644 packages/stdlib/src/structs/stack/index.ts diff --git a/packages/stdlib/src/structs/index.ts b/packages/stdlib/src/structs/index.ts new file mode 100644 index 0000000..d34d808 --- /dev/null +++ b/packages/stdlib/src/structs/index.ts @@ -0,0 +1 @@ +export * from './stack'; \ No newline at end of file diff --git a/packages/stdlib/src/structs/stack/index.test.ts b/packages/stdlib/src/structs/stack/index.test.ts new file mode 100644 index 0000000..b66dab2 --- /dev/null +++ b/packages/stdlib/src/structs/stack/index.test.ts @@ -0,0 +1,117 @@ +import { describe, it, expect } from 'vitest'; +import { Stack } from './index'; + +describe('Stack', () => { + describe('constructor', () => { + it('create an empty stack if no initial values are provided', () => { + const stack = new Stack(); + + expect(stack.length).toBe(0); + expect(stack.isEmpty).toBe(true); + }); + + it('create a stack with the provided initial values', () => { + const initialValues = [1, 2, 3]; + const stack = new Stack(initialValues); + + expect(stack.length).toBe(initialValues.length); + expect(stack.peek()).toBe(initialValues.at(-1)); + }); + + it('create a stack with the provided initial value', () => { + const initialValue = 1; + const stack = new Stack(initialValue); + + expect(stack.length).toBe(1); + expect(stack.peek()).toBe(initialValue); + }); + + it('create a stack with the provided options', () => { + const options = { maxSize: 5 }; + const stack = new Stack(undefined, options); + + expect(stack.length).toBe(0); + expect(stack.isFull).toBe(false); + }); + }); + + describe('push', () => { + it('push an element onto the stack', () => { + const stack = new Stack(); + stack.push(1); + + expect(stack.length).toBe(1); + expect(stack.peek()).toBe(1); + }); + + it('throw an error if the stack is full', () => { + const options = { maxSize: 1 }; + const stack = new Stack(undefined, options); + stack.push(1); + + expect(() => stack.push(2)).toThrow(new RangeError('Stack is full')); + }); + }); + + describe('pop', () => { + it('pop an element from the stack', () => { + const stack = new Stack([1, 2, 3]); + const poppedElement = stack.pop(); + + expect(poppedElement).toBe(3); + expect(stack.length).toBe(2); + }); + + it('return undefined if the stack is empty', () => { + const stack = new Stack(); + const poppedElement = stack.pop(); + + expect(poppedElement).toBeUndefined(); + }); + }); + + describe('peek', () => { + it('return the top element of the stack', () => { + const stack = new Stack([1, 2, 3]); + const topElement = stack.peek(); + + expect(topElement).toBe(3); + expect(stack.length).toBe(3); + }); + + it('throw an error if the stack is empty', () => { + const stack = new Stack(); + expect(() => stack.peek()).toThrow(new RangeError('Stack is empty')); + }); + }); + + describe('clear', () => { + it('clear the stack', () => { + const stack = new Stack([1, 2, 3]); + stack.clear(); + + expect(stack.length).toBe(0); + expect(stack.isEmpty).toBe(true); + }); + }); + + describe('iteration', () => { + it('iterate over the stack in LIFO order', () => { + const stack = new Stack([1, 2, 3]); + const elements = [...stack]; + + expect(elements).toEqual([3, 2, 1]); + }); + + it('iterate over the stack asynchronously in LIFO order', async () => { + const stack = new Stack([1, 2, 3]); + const elements: number[] = []; + + for await (const element of stack) { + elements.push(element); + } + + expect(elements).toEqual([3, 2, 1]); + }); + }); +}); \ No newline at end of file diff --git a/packages/stdlib/src/structs/stack/index.ts b/packages/stdlib/src/structs/stack/index.ts new file mode 100644 index 0000000..611e750 --- /dev/null +++ b/packages/stdlib/src/structs/stack/index.ts @@ -0,0 +1,144 @@ +export type StackOptions = { + maxSize?: number; +}; + +/** + * Represents a stack data structure + * @template T The type of elements stored in the stack + */ +export class Stack implements Iterable, AsyncIterable { + /** + * The maximum number of elements that the stack can hold + * + * @private + * @type {number} + */ + private maxSize: number; + + /** + * The stack data structure + * + * @private + * @type {T[]} + */ + private 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 = Array.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(): number { + return this.stack.length; + } + + /** + * Checks if the stack is empty + * @returns {boolean} `true` if the stack is empty, `false` otherwise + */ + public get isEmpty(): boolean { + return this.stack.length === 0; + } + + /** + * Checks if the stack is full + * @returns {boolean} `true` if the stack is full, `false` otherwise + */ + public get isFull(): boolean { + 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); + + return this; + } + + /** + * Pops an element from the stack + * @returns {T} The element popped from the stack + */ + public pop(): T | undefined { + return this.stack.pop(); + } + + /** + * Peeks at the top element of the stack + * @returns {T} The top element of the stack + */ + public peek(): T | undefined { + if (this.isEmpty) + throw new RangeError('Stack is empty'); + + return this.stack[this.stack.length - 1]; + } + + /** + * Clears the stack + * + * @returns {this} + */ + public clear() { + this.stack.length = 0; + + return this; + } + + /** + * Converts the stack to an array + * + * @returns {T[]} + */ + public toArray(): T[] { + return this.stack.slice(); + } + + /** + * Returns a string representation of the stack + * + * @returns {string} + */ + public toString() { + return this.stack.reverse().toString(); + } + + /** + * Returns an iterator for the stack + * + * @returns {IterableIterator} + */ + public [Symbol.iterator]() { + return this.stack.reverse()[Symbol.iterator](); + } + + /** + * Returns an async iterator for the stack + * + * @returns {AsyncIterableIterator} + */ + public async *[Symbol.asyncIterator]() { + for (const element of this.stack.reverse()) { + yield element; + } + } +}