mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 10:54:44 +00:00
feat(packages/stdlib): add stack data structure and tests
This commit is contained in:
1
packages/stdlib/src/structs/index.ts
Normal file
1
packages/stdlib/src/structs/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './stack';
|
||||
117
packages/stdlib/src/structs/stack/index.test.ts
Normal file
117
packages/stdlib/src/structs/stack/index.test.ts
Normal file
@@ -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<number>();
|
||||
|
||||
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<number>(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<number>();
|
||||
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<number>(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<number>([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<number>();
|
||||
const poppedElement = stack.pop();
|
||||
|
||||
expect(poppedElement).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('peek', () => {
|
||||
it('return the top element of the stack', () => {
|
||||
const stack = new Stack<number>([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<number>();
|
||||
expect(() => stack.peek()).toThrow(new RangeError('Stack is empty'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', () => {
|
||||
it('clear the stack', () => {
|
||||
const stack = new Stack<number>([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<number>([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<number>([1, 2, 3]);
|
||||
const elements: number[] = [];
|
||||
|
||||
for await (const element of stack) {
|
||||
elements.push(element);
|
||||
}
|
||||
|
||||
expect(elements).toEqual([3, 2, 1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
144
packages/stdlib/src/structs/stack/index.ts
Normal file
144
packages/stdlib/src/structs/stack/index.ts
Normal file
@@ -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<T> implements Iterable<T>, AsyncIterable<T> {
|
||||
/**
|
||||
* 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<T>}
|
||||
*/
|
||||
public [Symbol.iterator]() {
|
||||
return this.stack.reverse()[Symbol.iterator]();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an async iterator for the stack
|
||||
*
|
||||
* @returns {AsyncIterableIterator<T>}
|
||||
*/
|
||||
public async *[Symbol.asyncIterator]() {
|
||||
for (const element of this.stack.reverse()) {
|
||||
yield element;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user