mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-26 23:21:18 +08:00
Add new hash table methods.
This commit is contained in:
parent
f04626bc5c
commit
ecd8d22fc6
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user