Skip to content

Commit 1f4fb47

Browse files
committed
Implement the hash table data structure
1 parent 0654028 commit 1f4fb47

File tree

4 files changed

+193
-1
lines changed

4 files changed

+193
-1
lines changed
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Hash table
2+
3+
In computing, a hash table (hash map) is a data structure that implements an associative array abstract data type, a structure that can map keys to values. A hash table uses a hash function to compute an index, also called a hash code, into an array of buckets or slots, from which the desired value can be found. During lookup, the key is hashed and the resulting hash indicates where the corresponding value is stored.
4+
5+
Ideally, the hash function will assign each key to a unique bucket, but most hash table designs employ an imperfect hash function, which might cause hash collisions where the hash function generates the same index for more than one key. Such collisions are typically accommodated in some way.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { HashTable } from '../hashTable';
2+
3+
describe('HashTable', () => {
4+
it('should create hash table of certain size', () => {
5+
const defaultHashTable = new HashTable();
6+
expect(defaultHashTable.buckets.length).toBe(32);
7+
8+
const biggerHashTable = new HashTable(64);
9+
expect(biggerHashTable.buckets.length).toBe(64);
10+
});
11+
12+
it('should generate proper hash for specified keys', () => {
13+
const hashTable = new HashTable();
14+
15+
expect(hashTable.hash('a')).toBe(1);
16+
expect(hashTable.hash('b')).toBe(2);
17+
expect(hashTable.hash('abc')).toBe(6);
18+
});
19+
20+
it('should set, read and delete data with collisions', () => {
21+
const hashTable = new HashTable(3);
22+
23+
expect(hashTable.hash('a')).toBe(1);
24+
expect(hashTable.hash('b')).toBe(2);
25+
expect(hashTable.hash('c')).toBe(0);
26+
expect(hashTable.hash('d')).toBe(1);
27+
28+
hashTable.set('a', 'sky-old');
29+
hashTable.set('a', 'sky');
30+
hashTable.set('b', 'sea');
31+
hashTable.set('c', 'earth');
32+
hashTable.set('d', 'ocean');
33+
34+
expect(hashTable.has('x')).toBe(false);
35+
expect(hashTable.has('b')).toBe(true);
36+
expect(hashTable.has('c')).toBe(true);
37+
38+
const stringifier = (value) => `${value.key}:${value.value}`;
39+
40+
// expect(hashTable.buckets[0].toString(stringifier)).toBe('c:earth');
41+
// expect(hashTable.buckets[1].toString(stringifier)).toBe('a:sky,d:ocean');
42+
// expect(hashTable.buckets[2].toString(stringifier)).toBe('b:sea');
43+
44+
expect(hashTable.get('a')).toBe('sky');
45+
expect(hashTable.get('d')).toBe('ocean');
46+
expect(hashTable.get('x')).not.toBeDefined();
47+
48+
hashTable.delete('a');
49+
50+
expect(hashTable.delete('not-existing')).toBeNull();
51+
52+
expect(hashTable.get('a')).not.toBeDefined();
53+
expect(hashTable.get('d')).toBe('ocean');
54+
55+
hashTable.set('d', 'ocean-new');
56+
expect(hashTable.get('d')).toBe('ocean-new');
57+
});
58+
59+
it('should be possible to add objects to hash table', () => {
60+
const hashTable = new HashTable();
61+
62+
hashTable.set('objectKey', { prop1: 'a', prop2: 'b' });
63+
64+
const object = hashTable.get('objectKey');
65+
expect(object).toBeDefined();
66+
expect(object.prop1).toBe('a');
67+
expect(object.prop2).toBe('b');
68+
});
69+
70+
it('should track actual keys', () => {
71+
const hashTable = new HashTable(3);
72+
73+
hashTable.set('a', 'sky-old');
74+
hashTable.set('a', 'sky');
75+
hashTable.set('b', 'sea');
76+
hashTable.set('c', 'earth');
77+
hashTable.set('d', 'ocean');
78+
79+
expect(hashTable.getKeys()).toEqual(['a', 'b', 'c', 'd']);
80+
expect(hashTable.has('a')).toBe(true);
81+
expect(hashTable.has('x')).toBe(false);
82+
83+
hashTable.delete('a');
84+
85+
expect(hashTable.has('a')).toBe(false);
86+
expect(hashTable.has('b')).toBe(true);
87+
expect(hashTable.has('x')).toBe(false);
88+
});
89+
90+
it('should get all the values', () => {
91+
const hashTable = new HashTable(3);
92+
93+
hashTable.set('a', 'alpha');
94+
hashTable.set('b', 'beta');
95+
hashTable.set('c', 'gamma');
96+
97+
expect(hashTable.getValues()).toEqual(['gamma', 'alpha', 'beta']);
98+
});
99+
100+
it('should get all the values from empty hash table', () => {
101+
const hashTable = new HashTable();
102+
expect(hashTable.getValues()).toEqual([]);
103+
});
104+
105+
it('should get all the values in case of hash collision', () => {
106+
const hashTable = new HashTable(3);
107+
108+
// Keys `ab` and `ba` in current implementation should result in one hash (one bucket).
109+
// We need to make sure that several items from one bucket will be serialized.
110+
hashTable.set('ab', 'one');
111+
hashTable.set('ba', 'two');
112+
113+
hashTable.set('ac', 'three');
114+
115+
expect(hashTable.getValues()).toEqual(['one', 'two', 'three']);
116+
});
117+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
export class HashTable {
2+
buckets: Array<Array<any>>;
3+
keys: {};
4+
5+
constructor(bucketsLength: number = 32) {
6+
this.buckets = new Array<Array<any>>(bucketsLength);
7+
this.keys = {};
8+
}
9+
10+
hash(key: string): number {
11+
return key
12+
.split("")
13+
.map((k) => k.charCodeAt(0))
14+
.reduce((a, b) => a + b, 0) % this.buckets.length;
15+
}
16+
17+
set(key: string, value: any) {
18+
const hash = this.hash(key);
19+
if (this.has(key)) {
20+
for (var entry of this.buckets[hash]) {
21+
if (entry.key === key) {
22+
entry.value = value;
23+
}
24+
}
25+
} else {
26+
if (!this.buckets[hash]) {
27+
this.buckets[hash] = [];
28+
}
29+
this.buckets[hash].push({ key, value });
30+
}
31+
this.keys[key] = hash;
32+
}
33+
34+
has(key: string): boolean {
35+
return Object.hasOwnProperty.call(this.keys, key);
36+
}
37+
38+
get(key: string): any {
39+
const hash = this.hash(key);
40+
const bucket = this.buckets[hash];
41+
return bucket?.find((v) => v.key === key)?.value;
42+
}
43+
44+
delete(key: string) {
45+
if (!this.has(key)) {
46+
return null;
47+
}
48+
49+
const hash = this.hash(key);
50+
delete this.keys[key];
51+
if (!this.buckets[hash]) {
52+
return null;
53+
}
54+
55+
this.buckets[this.hash(key)] = [
56+
...this.buckets[this.hash(key)].filter((pair) => pair.key != key),
57+
];
58+
}
59+
60+
getKeys(): string[] {
61+
return Object.keys(this.keys);
62+
}
63+
64+
getValues(): any[] {
65+
return this.buckets.reduce((values, bucket) => {
66+
const bucketValues = bucket.map((pair) => pair.value);
67+
return values.concat(bucketValues);
68+
}, []);
69+
}
70+
}

src/data-structures/linked-list/__test__/linkedList.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ describe('LinkedList', () => {
147147
.prepend(nodeValue2);
148148

149149
const nodeStringifier = (value) => `${value.key}:${value.value}`;
150-
debugger;
150+
151151
expect(linkedList.toString(nodeStringifier)).toBe('key2:2,key1:1');
152152
});
153153

0 commit comments

Comments
 (0)