Add new hash table methods.

This commit is contained in:
Oleksii Trekhleb 2018-05-31 21:30:44 +03:00
parent f04626bc5c
commit ecd8d22fc6
2 changed files with 82 additions and 12 deletions

View File

@ -1,14 +1,29 @@
import LinkedList from '../linked-list/LinkedList'; 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; const defaultHashTableSize = 32;
export default class HashTable { export default class HashTable {
/**
* @param {number} hashTableSize
*/
constructor(hashTableSize = defaultHashTableSize) { constructor(hashTableSize = defaultHashTableSize) {
// Create hash table of certain size and fill each bucket with empty linked list. // Create hash table of certain size and fill each bucket with empty linked list.
this.buckets = Array(hashTableSize).fill(null).map(() => new LinkedList()); 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) { hash(key) {
const hash = Array.from(key).reduce( const hash = Array.from(key).reduce(
(hashAccumulator, keySymbol) => (hashAccumulator + keySymbol.charCodeAt(0)), (hashAccumulator, keySymbol) => (hashAccumulator + keySymbol.charCodeAt(0)),
@ -19,8 +34,14 @@ export default class HashTable {
return hash % this.buckets.length; 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 }); const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key });
if (!node) { if (!node) {
@ -32,8 +53,14 @@ export default class HashTable {
} }
} }
/**
* @param {string} key
* @return {*}
*/
delete(key) { 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 }); const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key });
if (node) { if (node) {
@ -43,10 +70,29 @@ export default class HashTable {
return null; return null;
} }
/**
* @param {string} key
* @return {*}
*/
get(key) { get(key) {
const bucketLinkedList = this.buckets[this.hash(key)]; const bucketLinkedList = this.buckets[this.hash(key)];
const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key }); const node = bucketLinkedList.find({ callback: nodeValue => nodeValue.key === key });
return node ? node.value.value : null; 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);
}
} }

View File

@ -17,7 +17,7 @@ describe('HashTable', () => {
expect(hashTable.hash('abc')).toBe(6); 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); const hashTable = new HashTable(3);
expect(hashTable.hash('a')).toBe(1); expect(hashTable.hash('a')).toBe(1);
@ -25,11 +25,15 @@ describe('HashTable', () => {
expect(hashTable.hash('c')).toBe(0); expect(hashTable.hash('c')).toBe(0);
expect(hashTable.hash('d')).toBe(1); expect(hashTable.hash('d')).toBe(1);
hashTable.insert('a', 'sky-old'); hashTable.set('a', 'sky-old');
hashTable.insert('a', 'sky'); hashTable.set('a', 'sky');
hashTable.insert('b', 'sea'); hashTable.set('b', 'sea');
hashTable.insert('c', 'earth'); hashTable.set('c', 'earth');
hashTable.insert('d', 'ocean'); 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}`; const stringifier = value => `${value.key}:${value.value}`;
@ -47,18 +51,38 @@ describe('HashTable', () => {
expect(hashTable.get('a')).toBeNull(); expect(hashTable.get('a')).toBeNull();
expect(hashTable.get('d')).toBe('ocean'); expect(hashTable.get('d')).toBe('ocean');
hashTable.insert('d', 'ocean-new'); hashTable.set('d', 'ocean-new');
expect(hashTable.get('d')).toBe('ocean-new'); expect(hashTable.get('d')).toBe('ocean-new');
}); });
it('should be possible to add objects to hash table', () => { it('should be possible to add objects to hash table', () => {
const hashTable = new HashTable(); const hashTable = new HashTable();
hashTable.insert('objectKey', { prop1: 'a', prop2: 'b' }); hashTable.set('objectKey', { prop1: 'a', prop2: 'b' });
const object = hashTable.get('objectKey'); const object = hashTable.get('objectKey');
expect(object).toBeDefined(); expect(object).toBeDefined();
expect(object.prop1).toBe('a'); expect(object.prop1).toBe('a');
expect(object.prop2).toBe('b'); 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();
});
}); });