diff --git a/.eslintrc.json b/.eslintrc.json index f94493c..9db8fd5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,7 +16,6 @@ "rules": { "eqeqeq": "off", "curly": "error", - "quotes": ["error", "single"], - "allowImplicit": true + "quotes": ["error", "single"] } } diff --git a/README.md b/README.md index a73a809..3d5fff5 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ This repository maintained by [Nick Scialli](https://twitter.com/nas5w) and powe - TODO: Binary Tree - [Graph](/src/dataStructures/graph.js) - [Hash Table](/src/dataStructures/hashTable.js) -- TODO: Heap +- [Heap](/src/dataStructures/Heap.js) - TODO: Tree - TODO: Graph - TODO: Disjoint Set diff --git a/src/dataStructures/Heap.js b/src/dataStructures/Heap.js new file mode 100644 index 0000000..0c39976 --- /dev/null +++ b/src/dataStructures/Heap.js @@ -0,0 +1,122 @@ +class Heap { + /** + * Constructor for Heap data structure. + * + * This is a max Heap supported by an Array. This can be modified into a min Heap + * by swapping the item comparisons. The Heap is intended for Number items, but can be + * modified for any item. + * + * @class + * + * @param {Number} i1 + * @param {Number} i2 + * @returns {void} + */ + constructor() { + Object.defineProperty(this, '_storage', { + value: new Array(1) + }); + } + + /** + * Returns number of items in Heap. + * + * @returns {Number} + */ + get length() { + return this._storage.length-1; + } + + /** + * Swaps items. + * + * @param {Number} i1 + * @param {Number} i2 + * @returns {void} + */ + _swap(i1, i2) { + [this._storage[i1], this._storage[i2]] = [this._storage[i2], this._storage[i1]]; + } + + /** + * Moves item up the heap to the correct position. + * + * @param {Number} index + * @returns {void} + */ + _rise(index) { + if (index === 1) { return; } // base case: top item + const parentIndex = Math.floor(index/2); // child items are 2*index and 2*index + 1 + // swap if < for max heap, > for min heap + if (this._storage[parentIndex] < this._storage[index]) { + this._swap(parentIndex, index); + this._rise(parentIndex); + } + } + + /** + * Moves item down the heap to the correct position. + * + * @param {Number} index + * @returns {void} + */ + _fall(index) { + if (2*index >= this._storage.length) { return; } // base case: no children + const childIndex = 2*index+1 >= this._storage.length ? + 2*index // one child (left) + : this._storage[2*index] > this._storage[2*index+1] ? // two children + 2*index + : 2*index+1; // compare with largest child + if (this._storage[childIndex] > this._storage[index]) { + this._swap(childIndex, index); + this._fall(childIndex); + } + } + + /** + * Returns true if there are no items in the Heap, otherwise false. + * + * @returns {Boolean} + */ + isEmpty() { + return this._storage.length === 1; + } + + /** + * Returns the top item in Heap. + * + * If the Heap is empty, returns undefined. + * + * @returns {Number} + */ + peek() { + return this._storage[1]; + } + + /** + * Returns and removes the top item in Heap. + * + * If the Heap is empty, returns undefined. + * + * @returns {Number} + */ + getMax() { + if (this.isEmpty()) { return; } + this._swap(1, this._storage.length-1); // swap top item and last item + const top = this._storage.pop(); + this._fall(1); + return top; + } + + /** + * Adds an item to the Heap. + * + * @returns {void} + */ + push(item) { + this._storage.push(item); + this._rise(this._storage.length-1); + } +} + +module.exports = Heap; diff --git a/test/dataStructures/Heap.test.js b/test/dataStructures/Heap.test.js new file mode 100644 index 0000000..7ae2fb7 --- /dev/null +++ b/test/dataStructures/Heap.test.js @@ -0,0 +1,148 @@ +const Heap = require('../../src/dataStructures/Heap'); + +describe('Heap constructor', () => { + it('should create empty Heap', () => { + const heap = new Heap(); + expect(heap._storage).toEqual([undefined]); + }); +}); + +describe('Heap public method isEmpty', () => { + it('should return true if no items in Heap', () => { + const heap = new Heap(); + expect(heap.isEmpty()).toBe(true); + }); + + it('should return false if one or more items in Heap', () => { + const heap = new Heap(); + heap._storage[1] = 1; + expect(heap.isEmpty()).toBe(false); + heap._storage[2] = 1; + expect(heap.isEmpty()).toBe(false); + }); +}); + +describe('Heap public method peek', () => { + it('should return undefined if Heap is empty', () => { + const heap = new Heap(); + expect(heap.peek()).toBeUndefined(); + }); + + it('should return the top item', () => { + const heap = new Heap(); + heap._storage[1] = 1; + heap._storage[2] = 2; + heap._storage[3] = 3; + expect(heap.peek()).toBe(1); + }); +}); + +describe('Heap public method getMax', () => { + it('should return undefined if Heap is empty', () => { + const heap = new Heap(); + expect(heap.getMax()).toBeUndefined(); + }); + + it('should return and remove the top item', () => { + const heap = new Heap(); + heap._storage[1] = 3; + heap._storage[2] = 2; + heap._storage[3] = 1; + const top = heap.getMax(); + expect(top).toBe(3); + expect(heap._storage).toEqual([undefined, 2, 1]); + }); +}); + +describe('Heap public method count', () => { + it('should return 0 for no items', () => { + const heap = new Heap(); + expect(heap.length).toBe(0); + }); + + it('should return the number of items in Heap', () => { + const heap = new Heap(); + heap._storage[1] = 2; + expect(heap.length).toBe(1); + heap._storage[2] = 1; + expect(heap.length).toBe(2); + }); +}); + +describe('Heap public method push', () => { + it('should increase the number of items in the Heap', () => { + const heap = new Heap(); + expect(heap.length).toBe(0); + heap.push(1); + expect(heap.length).toBe(1); + }); + + it('should add items in the correct position', () => { + const heap = new Heap(); + heap.push(1); + expect(heap._storage).toEqual([undefined, 1]); + heap.push(2); + expect(heap._storage).toEqual([undefined, 2, 1]); + heap.push(3); + expect(heap._storage).toEqual([undefined, 3, 1, 2]) + }); +}); + +describe('Heap private method rise', () => { + it('should not move top item', () => { + const heap = new Heap(); + heap._storage[1] = 1; + heap._storage[2] = 2; + heap._rise(1); + expect(heap._storage).toEqual([undefined, 1, 2]); + }); + + it('should not move items in the correct position', () => { + const heap = new Heap(); + heap._storage[1] = 2; + heap._storage[2] = 1; + heap._storage[3] = 3; + heap._rise(2); + expect(heap._storage).toEqual([undefined, 2, 1, 3]); + }); + + it('should move large items up', () => { + const heap = new Heap(); + heap._storage[1] = 1; + heap._storage[2] = 2; + heap._storage[3] = 3; + heap._rise(2); + expect(heap._storage).toEqual([undefined, 2, 1, 3]); + heap._rise(3); + expect(heap._storage).toEqual([undefined, 3, 1, 2]); + }); +}); + +describe('Heap private method fall', () => { + it('should not move items with no children', () => { + const heap = new Heap(); + heap._storage[1] = 1; + heap._storage[2] = 2; + heap._storage[3] = 3; + heap._fall(3); + expect(heap._storage).toEqual([undefined, 1, 2, 3]); + }); + + it('should not move items in the correct position', () => { + const heap = new Heap(); + heap._storage[1] = 4; + heap._storage[2] = 2; + heap._storage[3] = 3; + heap._fall(1); + expect(heap._storage).toEqual([undefined, 4, 2, 3]); + }); + + it('should move small items down', () => { + const heap = new Heap(); + heap._storage[1] = 1; + heap._storage[2] = 2; + heap._storage[3] = 3; + heap._fall(1); + expect(heap._storage).toEqual([undefined, 3, 2, 1]); + }); +});