Skip to content

Commit

Permalink
feature: stack (#17)
Browse files Browse the repository at this point in the history
* chore(comments): update LL comments

* feat(stack): implement Stack data structure in TypeScript

* test(stack): add test for Stack data structure
  • Loading branch information
joshuagraber committed Aug 28, 2023
1 parent 525cf9a commit 55830a9
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 14 deletions.
28 changes: 14 additions & 14 deletions src/data-structures/sequences/linked-list/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class LinkedList<T> implements Iterable<T> {
}

/**
* Appends values to list from array
* Appends values to list from array ~ O(k)
*/
fromArray(arr: T[]): LinkedList<T> {
for (const val of arr) {
Expand All @@ -71,7 +71,7 @@ class LinkedList<T> implements Iterable<T> {
// INSERT
/**
* Adds node to the head ~ O(1)
* @param {T} val - value to add to head of list
* @param {T} val value to add to head of list
* @return {boolean}
*/
unshift(val: T): boolean {
Expand All @@ -96,7 +96,7 @@ class LinkedList<T> implements Iterable<T> {

/**
* Adds node to the tail ~ O(1)
* @param {T} val - value to add to tail of list
* @param {T} val value to add to tail of list
* @return {boolean}
*/
push(val: T): boolean {
Expand All @@ -121,8 +121,8 @@ class LinkedList<T> implements Iterable<T> {

/**
* Adds a node at specified index ~ O(n)
* @param {number} i - index
* @param {T} val - value to add to list
* @param {number} i index
* @param {T} val value to add to list
* @return {boolean}
*/
addAt(i: number, val: T): boolean {
Expand Down Expand Up @@ -175,7 +175,7 @@ class LinkedList<T> implements Iterable<T> {

/**
* Gets the value of node at index i - O(n)
* @param {number} i - index of element
* @param {number} i index of element
* @returns {T | null} value of element at index i if list is defined and i exists in list
*/
get(i: number): T | null {
Expand All @@ -198,7 +198,7 @@ class LinkedList<T> implements Iterable<T> {
// SEARCH
/**
* Returns the index of the first occurrence of the specified value in the linked list.
* @param {T} value - value to search for
* @param {T} value value to search for
* @return {number} the index of the first occurrence of the element, and -1 if the element does not exist.
*/
indexOf(value: T): number {
Expand All @@ -219,7 +219,7 @@ class LinkedList<T> implements Iterable<T> {

/**
* Checks if value is a node in linked list.
* @param {T} value - value to search for
* @param {T} value value to search for
* @returns {boolean}
*/
contains(value: T): boolean {
Expand All @@ -229,7 +229,7 @@ class LinkedList<T> implements Iterable<T> {
// DELETE
/**
* Removes node at head ~ O(1)
* @return {T | null} - value of removed head node if list is defined
* @return {T | null} value of removed head node if list is defined
*/
shift(): T | null {
if (!this.list) return null;
Expand All @@ -254,7 +254,7 @@ class LinkedList<T> implements Iterable<T> {

/**
* Removes node at tail ~ O(1)
* @return {T | null} - value of removed head
* @return {T | null} value of removed head
*/
pop(): T | null {
if (!this.list) return null;
Expand All @@ -278,8 +278,8 @@ class LinkedList<T> implements Iterable<T> {

/**
* Removes node at specified index ~ O(n)
* @param {number} i - index to remove
* @return {T | null} - value of removed node
* @param {number} i index to remove
* @return {T | null} value of removed node
*/
removeAt(i: number): T | null {
if (!this.list) return null;
Expand Down Expand Up @@ -317,8 +317,8 @@ class LinkedList<T> implements Iterable<T> {
/**
* Removes first occurrence of node with specified value. Returns value of removed node if
* removal was successful, and null otherwise. ~ O(n)
* @param {T} val - value to remove
* @returns {T | null} - value of removed node
* @param {T} val value to remove
* @returns {T | null} value of removed node
*/
remove(val: T): T | null {
const index = this.indexOf(val); // O(n)
Expand Down
92 changes: 92 additions & 0 deletions src/data-structures/sequences/stack/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { LinkedList } from '../linked-list';
import { type TypedEqualityFunction } from '../../utils';

class Stack<T> implements Iterable<T> {
private list: LinkedList<T>;

constructor(equalsFunction?: TypedEqualityFunction<T>) {
if (typeof equalsFunction === 'function') {
this.list = new LinkedList(equalsFunction);
} else {
this.list = new LinkedList();
}
}

// HELPERS
/**
* Returns size ~ O(1)
*/
size(): number {
return this.list.size();
}

/**
* Returns true if stack is empty ~ O(1)
*/
isEmpty(): boolean {
return this.list.isEmpty();
}

/**
* Appends values to stack from array ~ O(k)
*/
fromArray(arr: T[]): Stack<T> {
this.list = this.list.fromArray(arr);
return this;
}

/*
* Iterator
*/

[Symbol.iterator](): Iterator<T> {
return this.list[Symbol.iterator]();
}

// INSERT
/**
* Adds new element ~ O(1)
* @param {T} element value to add to stack
* @return {boolean}
*/
push(element: T) {
return this.list.push(element);
}

// ACCESS
/**
* Gets the value of element at top of stack ~ O(1)
* @returns {T | null} value of top element in stack
*/
peek(): T | null {
return this.list.peekBack();
}

// SEARCH
/**
* Checks if value exists in the stack. ~ O(n)
* @param {T} element value to search for
* @returns {boolean}
*/
contains(element: T) {
return this.list.contains(element);
}

// DELETE
/**
* Removes element ~ O(1)
* @return {T | null} value of removed element
*/
pop(): T | null {
return this.list.pop();
}

/**
* Deletes all elements ~ O(1)
*/
clear() {
this.list.clear();
}
}

export { Stack };
144 changes: 144 additions & 0 deletions test/data-structures/sequences/stack.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Stack } from '../../../src/data-structures/sequences/stack';

describe('Stack', () => {
let stack: Stack<number>;

beforeEach(() => {
stack = new Stack();
});

describe('empty stack', () => {
it('returns null when pop() is called on empty stack', () => {
expect(stack.pop()).toBe(null);
});

it('returns null when peek() is called on empty stack', () => {
expect(stack.peek()).toBe(null);
});
});

it('is empty', () => {
expect(stack.isEmpty()).toBe(true);
});

it('pushes', () => {
stack.push(1);
expect(stack.size()).toBe(1);

stack.push(2);
expect(stack.size()).toBe(2);

stack.push(3);
expect(stack.size()).toBe(3);
});

it('finds out if list contains element', () => {
expect(stack.contains(1)).toBe(false);
stack.push(1);
stack.push(2);
stack.push(3);

expect(stack.contains(1)).toBe(true);
expect(stack.contains(3)).toBe(true);
expect(stack.contains(8)).toBe(false);
});

it('pops', () => {
stack.push(1);
stack.push(2);
stack.push(3);

stack.pop();
expect(stack.size()).toBe(2);

stack.pop();
expect(stack.size()).toBe(1);

stack.pop();
expect(stack.size()).toBe(0);
});

it('peeks', () => {
stack.push(1);
expect(stack.peek()).toBe(1);

stack.push(2);
expect(stack.peek()).toBe(2);
});

it('clears the stack', () => {
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.clear();
expect(stack.isEmpty()).toBe(true);

stack.push(1);
stack.clear();
expect(stack.isEmpty()).toBe(true);

stack.clear();
expect(stack.isEmpty()).toBe(true);
});

it('fills a new stack from an array', () => {
stack.fromArray([1, 3, 7, 6]);

expect(stack.size()).toBe(4);
expect(stack.pop()).toBe(6);
});

it('is iterable', () => {
const nums = [1, 2, 3];

for (const n of nums) {
stack.push(n);
}

let i = 0;
for (const n of stack) {
expect(n).toBe(nums[i]);
i += 1;
}
});
});

describe('Stack - complex object', () => {
class Car {
carId: number;
topSpeed: number;
engineSize: number;

constructor(id: number) {
this.carId = id;
this.topSpeed = 100;
this.engineSize = 100;
}
}

const sameHeroF = (a: Car, b: Car) => a.carId === b.carId;

let stack: Stack<Car>;

beforeAll(() => {
const ferrari = new Car(123);
const peugeot = new Car(456);
const honda = new Car(789);

stack = new Stack(sameHeroF);

stack.push(ferrari);
stack.push(peugeot);
stack.push(honda);
});

it('checks if stack contains hero', () => {
const knight = new Car(123);
const mage = new Car(789);

expect(stack.contains(knight)).toBe(true);
expect(stack.contains(mage)).toBe(true);
expect(stack.contains(new Car(246))).toBe(false);
});
});

0 comments on commit 55830a9

Please sign in to comment.