diff --git a/README.md b/README.md index 839f8a4..f354688 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Data structures covered so far - - [Graph](#graph) - [Queue](#queue) - [Linked List](#link-list) +- [Stack](#stack) # Contribution Your contribution is highly appreciated. You can contribute in several ways - @@ -242,3 +243,37 @@ Usage as spread operator ```js const items = [...list] // ['firstVal', 'Mid', 'xyz', 'secondVal'] ``` + +# Stack (Linked List Implementation) + +Import Stack data structure and create a list object. + +```js +import { Stack } from '@js-labs/data-structures/lib/ds'; +// const { Stack } = require('@js-labs/data-structures/lib/ds') +const stack = new Stack; +``` + +Get size of the stack and check if the stack is empty + +```js +stack.size() //0 +stack.isEmpty() //true +``` + +Push items in the stack + +```js +stack.push('item1'); +stack.size() //1 + +stack.push('item2'); +stack.size() //2 +``` + +Pop items from the stack + +```js +stack.pop() //item2 +stack.pop() //item1 +``` \ No newline at end of file diff --git a/src/index.js b/src/index.js index 00dfb40..8d10f02 100644 --- a/src/index.js +++ b/src/index.js @@ -2,4 +2,5 @@ export * from './queue/queue'; export * from './binary-search-tree/binary-search-tree'; export * from './graph/graph'; export * from './graph/adj-list'; -export * from './linked-list/linked-list'; \ No newline at end of file +export * from './linked-list/linked-list'; +export * from './stack/stack'; \ No newline at end of file diff --git a/src/linked-list/linked-list.js b/src/linked-list/linked-list.js index 0143720..59ef8f6 100644 --- a/src/linked-list/linked-list.js +++ b/src/linked-list/linked-list.js @@ -18,11 +18,13 @@ export class Node { const _size = Symbol('size'); const _head = Symbol('head'); +const _tail = Symbol('tail'); export class LinkedList { constructor() { this[_size] = 0; this[_head] = null; + this[_tail] = null; } *[Symbol.iterator]() { @@ -38,6 +40,10 @@ export class LinkedList { return this[_head]; } + get tail() { + return this[_tail]; + } + size() { return this[_size]; } @@ -51,33 +57,43 @@ export class LinkedList { } insert(item) { - if(this.isEmpty()) { + if (this.isEmpty()) { this.insertFirst(item); } else { this.insertLast(item); } - this[_size] += 1; + } insertLast(item) { let el = this[_head]; - while(el && el.next !== null) { + while (el && el.next !== null) { el = el.next; } el.next = new Node(item); + this[_tail] = el.next; + this[_size] += 1; } insertFirst(item) { - this[_head] = new Node(item); + if (this.isEmpty()) { + this[_head] = new Node(item); + this[_tail] = this[_head]; + } else { + const currNode = this[_head]; + this[_head] = new Node(item); + this[_head].next = currNode; + } + this[_size] += 1; } insertBefore(item, before) { - const {prev, curr, found} = this._search(before); + const { prev, curr, found } = this._search(before); - if(found) { + if (found) { const node = new Node(item); - if(prev === null) { + if (prev === null) { this[_head] = node; } else { prev.next = node; @@ -87,31 +103,50 @@ export class LinkedList { } } insertAfter(item, after) { - const {curr, next, found} = this._search(after); + const { curr, next, found } = this._search(after); - if(found) { + if (found) { curr.next = new Node(item); curr.next.next = next; + if (next === null) { + this[_tail] = curr.next; + } this[_size] += 1; } } remove(item) { - const {prev, next, found} = this._search(item); - if(found) { - if(prev === null) { + const { prev, next, found } = this._search(item); + if (found) { + if (prev === null) { this[_head] = next; } else { prev.next = next; + if (next === null) { + this[_tail] = prev; + } } this[_size] -= 1; } } + removeFirst() { + if (!this.isEmpty()) { + const currNode = this[_head]; + this[_head] = currNode.next; + currNode.next = null; + this[_size] -= 1; + if (this[_head] === null) { + this[_tail] = null; + } + return currNode; + } + } + _search(item) { let prev = null, curr = this.head, next = null, found = false; - while(curr !== null && !found) { + while (curr !== null && !found) { // TODO: Check for Object and Array as well - if(curr.key === item) { + if (curr.key === item) { next = curr.next; found = true; break; @@ -120,6 +155,6 @@ export class LinkedList { curr = curr.next; } - return {prev, curr, next, found}; + return { prev, curr, next, found }; } } diff --git a/src/linked-list/linked-list.spec.js b/src/linked-list/linked-list.spec.js index 3ef057e..5691bc7 100644 --- a/src/linked-list/linked-list.spec.js +++ b/src/linked-list/linked-list.spec.js @@ -23,6 +23,13 @@ describe('Linked List', () => { expect(list.isEmpty()).toBeFalsy(); expect(list.head.key).toContain('firstVal'); expect(list.head.next).toBe(null); + expect(list.tail.key).toBe('firstVal'); + + list.insertFirst('secondFirst'); + expect(list.size()).toBe(2); + expect(list.head.key).toContain('secondFirst'); + expect(list.head.next.key).toContain('firstVal'); + expect(list.tail.key).toBe('firstVal'); }); describe('When first item is inserted', () => { @@ -40,16 +47,21 @@ describe('Linked List', () => { expect(list.size()).toBe(2); expect(list.head.next.key).toBe('secondVal'); expect(list.head.next.next).toBe(null); + expect(list.tail.key).toBe('secondVal'); }); it('should insert item after specified item', () => { + expect(list.tail.key).toBe('firstVal'); + list.insertAfter('secondVal', 'firstVal'); expect(list.size()).toBe(2); expect(list.search('secondVal')).toBeTruthy(); + expect(list.tail.key).toBe('secondVal'); list.insertAfter('thirdVal', 'xyz'); expect(list.size()).toBe(2); expect(list.search('thirdVal')).toBeFalsy(); + expect(list.tail.key).toBe('secondVal'); list.insertAfter('thirdVal', 'firstVal'); expect(list.size()).toBe(3); @@ -93,18 +105,44 @@ describe('Linked List', () => { list.insert('B'); list.insert('C'); expect(list.size()).toBe(3); + expect(list.tail.key).toBe('C'); list.remove('B'); expect(list.size()).toBe(2); expect(list.search('B')).toBeFalsy(); - list.remove('A'); + list.remove('C'); expect(list.size()).toBe(1); - expect(list.search('A')).toBeFalsy(); + expect(list.search('C')).toBeFalsy(); + expect(list.tail.key).toBe('A'); - list.remove('C'); + list.remove('A'); expect(list.size()).toBe(0); - expect(list.search('C')).toBeFalsy(); + expect(list.search('A')).toBeFalsy(); }); + it('should remove item from the head', () => { + expect(list.size()).toBe(0); + + list.insert('A'); + list.insert('B'); + list.insert('C'); + expect(list.size()).toBe(3); + expect(list.tail.key).toBe('C'); + + let item = list.removeFirst(); + expect(item.key).toBe('A'); + expect(list.tail.key).toBe('C'); + expect(list.size()).toBe(2); + + item = list.removeFirst(); + expect(item.key).toBe('B'); + expect(list.tail.key).toBe('C'); + expect(list.size()).toBe(1); + + item = list.removeFirst(); + expect(item.key).toBe('C'); + expect(list.tail).toEqual(null); + expect(list.size()).toBe(0); + }); }); diff --git a/src/stack/stack.js b/src/stack/stack.js new file mode 100644 index 0000000..09d210c --- /dev/null +++ b/src/stack/stack.js @@ -0,0 +1,60 @@ +const _stack = Symbol('stack'); +import { LinkedList } from "../linked-list/linked-list"; + +/** + * Linked List implementation of Stack + */ +export class Stack { + constructor() { + this[_stack] = new LinkedList; + } + + /** + * getter for _stack object + */ + get stack() { + return this[_stack]; + } + + /** + * Push an item on top of the stack. + * Ideally the insertFirst method of linked list is called to add the item in the first position in linked list + * @param {any} item + */ + push(item) { + this[_stack].insertFirst(item); + } + + /** + * Pop the top item from the stack + * Ideally removeFirst method of stack is called + */ + pop() { + if (!this.isEmpty()) { + return this[_stack].removeFirst().key; + } + } + + /** + * Returns the top item of the stack without popping it + */ + peek() { + if (!this.isEmpty()) { + return this[_stack].head.key; + } + } + + /** + * Returns the size of the stack + */ + size() { + return this[_stack].size(); + } + + /** + * Checks if the stack is empty + */ + isEmpty() { + return this.size() === 0; + } +} \ No newline at end of file diff --git a/src/stack/stack.spec.js b/src/stack/stack.spec.js new file mode 100644 index 0000000..026abf7 --- /dev/null +++ b/src/stack/stack.spec.js @@ -0,0 +1,61 @@ +import { Stack } from "./stack"; + +describe('Linked List Implementation of Stacks', () => { + let stack; + beforeEach(() => { + stack = new Stack; + }); + + it('should create a stack object', () => { + expect(stack).toBeDefined(); + expect(stack.size()).toBe(0); + expect(stack.isEmpty()).toBeTruthy(); + }); + + describe('push operation', () => { + it('should insert element in the stack', () => { + stack.push('A'); + expect(stack.size()).toBe(1); + expect(stack.isEmpty()).toBeFalsy(); + + stack.push('B'); + expect(stack.size()).toBe(2); + + stack.push('C'); + stack.push('D'); + stack.push('E'); + stack.push('F'); + expect(stack.size()).toBe(6); + }); + }); + + describe('pop and peek opetarion', () => { + beforeEach(() => { + stack.push('A'); + stack.push('B'); + stack.push('C'); + stack.push('D'); + stack.push('E'); + stack.push('F'); + }); + + it('should pop elements out of the stack', () => { + expect(stack.size()).toBe(6); + expect(stack.peek()).toBe('F'); + expect(stack.pop()).toBe('F'); + expect(stack.size()).toBe(5); + + expect(stack.peek()).toBe('E'); + expect(stack.pop()).toBe('E'); + expect(stack.size()).toBe(4); + + stack.pop(); + stack.pop(); + stack.pop(); + + expect(stack.peek()).toBe('A'); + expect(stack.pop()).toBe('A'); + expect(stack.size()).toBe(0); + }); + }); +}); \ No newline at end of file diff --git a/src/test-index.js b/src/test-index.js index 66b0a60..0a92288 100644 --- a/src/test-index.js +++ b/src/test-index.js @@ -3,3 +3,4 @@ require('./graph/graph.spec'); require('./graph/adj-list.spec'); require('./queue/queue.spec'); require('./linked-list/linked-list.spec'); +require('./stack/stack.spec');