diff --git a/src/data-structures/hash-table/HashTable.js b/src/data-structures/hash-table/HashTable.js index d241dfb8..785efff8 100644 --- a/src/data-structures/hash-table/HashTable.js +++ b/src/data-structures/hash-table/HashTable.js @@ -1,14 +1,29 @@ import LinkedList from '../linked-list/LinkedList'; +// Hash table size directly affects on the number of collisions. +// The bigger the hash table size the less collisions you'll get. +// For demonstrating purposes hash table size is small to show how collisions +// are being handled. const defaultHashTableSize = 32; export default class HashTable { + /** + * @param {number} hashTableSize + */ constructor(hashTableSize = defaultHashTableSize) { // Create hash table of certain size and fill each bucket with empty linked list. this.buckets = Array(hashTableSize).fill(null).map(() => new LinkedList()); + + // Just to keep track of all actual keys in a fast way. + this.keys = {}; } - // Converts key string to hash number. + /** + * Converts key string to hash number. + * + * @param {string} key + * @return {number} + */ hash(key) { const hash = Array.from(key).reduce( (hashAccumulator, keySymbol) => (hashAccumulator + keySymbol.charCodeAt(0)), @@ -19,8 +34,14 @@ export default class HashTable { return hash % this.buckets.length; } - insert(key, value) { - const bucketLinkedList = this.buckets[this.hash(key)]; + /** + * @param {string} key + * @param {*} value + */ + set(key, value) { + const keyHash = this.hash(key); + this.keys[key] = keyHash; + const bucketLinkedList = this.buckets[keyHash]; const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); if (!node) { @@ -32,8 +53,14 @@ export default class HashTable { } } + /** + * @param {string} key + * @return {*} + */ delete(key) { - const bucketLinkedList = this.buckets[this.hash(key)]; + const keyHash = this.hash(key); + delete this.keys[key]; + const bucketLinkedList = this.buckets[keyHash]; const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); if (node) { @@ -43,10 +70,29 @@ export default class HashTable { return null; } + /** + * @param {string} key + * @return {*} + */ get(key) { const bucketLinkedList = this.buckets[this.hash(key)]; const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); return node ? node.value.value : null; } + + /** + * @param {string} key + * @return {boolean} + */ + has(key) { + return Object.hasOwnProperty.call(this.keys, key); + } + + /** + * @return {string[]} + */ + getKeys() { + return Object.keys(this.keys); + } } diff --git a/src/data-structures/hash-table/__test__/HashTable.test.js b/src/data-structures/hash-table/__test__/HashTable.test.js index a39fcd24..3c77769a 100644 --- a/src/data-structures/hash-table/__test__/HashTable.test.js +++ b/src/data-structures/hash-table/__test__/HashTable.test.js @@ -17,7 +17,7 @@ describe('HashTable', () => { expect(hashTable.hash('abc')).toBe(6); }); - it('should insert, read and delete data with collisions', () => { + it('should set, read and delete data with collisions', () => { const hashTable = new HashTable(3); expect(hashTable.hash('a')).toBe(1); @@ -25,11 +25,15 @@ describe('HashTable', () => { expect(hashTable.hash('c')).toBe(0); expect(hashTable.hash('d')).toBe(1); - hashTable.insert('a', 'sky-old'); - hashTable.insert('a', 'sky'); - hashTable.insert('b', 'sea'); - hashTable.insert('c', 'earth'); - hashTable.insert('d', 'ocean'); + hashTable.set('a', 'sky-old'); + hashTable.set('a', 'sky'); + hashTable.set('b', 'sea'); + hashTable.set('c', 'earth'); + hashTable.set('d', 'ocean'); + + expect(hashTable.has('x')).toBeFalsy(); + expect(hashTable.has('b')).toBeTruthy(); + expect(hashTable.has('c')).toBeTruthy(); const stringifier = value => `${value.key}:${value.value}`; @@ -47,18 +51,38 @@ describe('HashTable', () => { expect(hashTable.get('a')).toBeNull(); expect(hashTable.get('d')).toBe('ocean'); - hashTable.insert('d', 'ocean-new'); + hashTable.set('d', 'ocean-new'); expect(hashTable.get('d')).toBe('ocean-new'); }); it('should be possible to add objects to hash table', () => { const hashTable = new HashTable(); - hashTable.insert('objectKey', { prop1: 'a', prop2: 'b' }); + hashTable.set('objectKey', { prop1: 'a', prop2: 'b' }); const object = hashTable.get('objectKey'); expect(object).toBeDefined(); expect(object.prop1).toBe('a'); expect(object.prop2).toBe('b'); }); + + it('should track actual keys', () => { + const hashTable = new HashTable(3); + + hashTable.set('a', 'sky-old'); + hashTable.set('a', 'sky'); + hashTable.set('b', 'sea'); + hashTable.set('c', 'earth'); + hashTable.set('d', 'ocean'); + + expect(hashTable.getKeys()).toEqual(['a', 'b', 'c', 'd']); + expect(hashTable.has('a')).toBeTruthy(); + expect(hashTable.has('x')).toBeFalsy(); + + hashTable.delete('a'); + + expect(hashTable.has('a')).toBeFalsy(); + expect(hashTable.has('b')).toBeTruthy(); + expect(hashTable.has('x')).toBeFalsy(); + }); });