Skip to content

Commit 446bbab

Browse files
committed
chapter 8: dictionary and hash table
1 parent fe6f722 commit 446bbab

13 files changed

+619
-0
lines changed

src/06-linked-list/linked-list_.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ class LinkedList {
126126
return this.#size;
127127
}
128128

129+
forEach(callback) {
130+
let current = this.#head;
131+
let index = 0;
132+
while (current) {
133+
callback(current.data, index);
134+
current = current.next;
135+
index++;
136+
}
137+
}
138+
129139
toString() {
130140
let current = this.#head;
131141
let objString = '';
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// src/08-dictionary-hash/01-using-dictionary-class.js
2+
3+
const Dictionary = require('./dictionary');
4+
5+
const translations = new Dictionary();
6+
7+
// Add some translations - English to Portuguese
8+
translations.set("hello", "olá");
9+
translations.set("thank you", "obrigado");
10+
translations.set("book", "livro");
11+
translations.set("cat", "gato");
12+
translations.set("computer", "computador");
13+
14+
// User interaction
15+
function translateWord(word) {
16+
if (translations.hasKey(word)) {
17+
const translation = translations.get(word);
18+
console.log(`The translation of "${word}" is "${translation}"`);
19+
} else {
20+
console.log(`Sorry, no translation found for "${word}"`);
21+
}
22+
}
23+
24+
// Example usage
25+
translateWord("hello"); // Output: The translation of "hello" is "olá"
26+
translateWord("dog"); // Output: Sorry, no translation found for "dog"
27+
28+
// Get all translations
29+
console.log("All translations:", translations.values());
30+
// All translations: [ 'olá', 'obrigado', 'livro', 'gato', 'computador' ]
31+
32+
// Get all words
33+
console.log("All words:", translations.keys());
34+
// All words: [ 'hello', 'thank you', 'book', 'cat', 'computer' ]
35+
36+
// Iterate through all translations
37+
translations.forEach((value, key) => {
38+
console.log(`${key}: ${value}`);
39+
});
40+
41+
42+
// to see the output of this file use the command: node src/08-dictionary-hash/01-using-dictionary-class.js
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// src/08-dictionary-hash/02-using-map-class.js
2+
3+
const translations = new Map();
4+
5+
// Add some translations - English to Portuguese
6+
translations.set("hello", "olá");
7+
translations.set("thank you", "obrigado");
8+
translations.set("book", "livro");
9+
translations.set("cat", "gato");
10+
translations.set("computer", "computador");
11+
12+
// User interaction
13+
function translateWord(word) {
14+
if (translations.has(word)) {
15+
const translation = translations.get(word);
16+
console.log(`The translation of "${word}" is "${translation}"`);
17+
} else {
18+
console.log(`Sorry, no translation found for "${word}"`);
19+
}
20+
}
21+
22+
// Example usage
23+
translateWord("hello"); // Output: The translation of "hello" is "olá"
24+
translateWord("dog"); // Output: Sorry, no translation found for "dog"
25+
26+
// Get all translations
27+
console.log("All translations:", translations.values());
28+
// All translations: [ 'olá', 'obrigado', 'livro', 'gato', 'computador' ]
29+
30+
// Get all words
31+
console.log("All words:", translations.keys());
32+
// All words: [ 'hello', 'thank you', 'book', 'cat', 'computer' ]
33+
34+
// Iterate through all translations
35+
translations.forEach((value, key) => {
36+
console.log(`${key}: ${value}`);
37+
});
38+
39+
40+
// to see the output of this file use the command: node src/08-dictionary-hash/01-using-dictionary-class.js
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const privateData = new WeakMap();
2+
3+
class Person {
4+
constructor(name, age) {
5+
this.name = name;
6+
this.age = age;
7+
privateData.set(this, { ssn: 'XXX-XX-XXXX', medicalHistory: [] });
8+
}
9+
10+
getSSN() {
11+
return privateData.get(this)?.ssn;
12+
}
13+
}
14+
15+
const alice = new Person("Penelope", 20);
16+
console.log(alice.name); // Penelope
17+
console.log(alice.getSSN()); // XXX-XX-XXXX
18+
19+
// Try to access private data
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// src/08-dictionary-hash/04-using-hashmap-class.js
2+
3+
const HashTable = require('./hash-table');
4+
5+
const addressBook = new HashTable();
6+
7+
// Add contacts
8+
addressBook.put('Gandalf', '[email protected]');
9+
addressBook.put('John', '[email protected]');
10+
addressBook.put('Tyrion', '[email protected]');
11+
12+
// Retrieve the hash code of a contact
13+
console.log(addressBook.hash('Gandalf')); // 19
14+
console.log(addressBook.hash('John')); // 29
15+
console.log(addressBook.hash('Tyrion')); // 16
16+
17+
// Retrieve contacts
18+
console.log(addressBook.get('Gandalf')); // gandalf@email.com
19+
console.log(addressBook.get('Loiane')); // undefined
20+
21+
// Remove contacts
22+
console.log(addressBook.remove('Gandalf')); // true
23+
console.log(addressBook.get('Gandalf')); // undefined
24+
25+
26+
// to see the output of this file use the command: node src/08-dictionary-hash/04-using-hashmap-class.js
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// src/08-dictionary-hash/05-hashmap-collision.js
2+
3+
const HashTable = require('./hash-table');
4+
5+
const addressBook = new HashTable();
6+
7+
// Add contacts
8+
addressBook.put('Ygritte', '[email protected]');
9+
addressBook.put('Jonathan', '[email protected]');
10+
addressBook.put('Jamie', '[email protected]');
11+
addressBook.put('Jack', '[email protected]');
12+
addressBook.put('Jasmine', '[email protected]');
13+
addressBook.put('Jake', '[email protected]');
14+
addressBook.put('Nathan', '[email protected]');
15+
addressBook.put('Athelstan', '[email protected]');
16+
addressBook.put('Sue', '[email protected]');
17+
addressBook.put('Aethelwulf', '[email protected]');
18+
addressBook.put('Sargeras', '[email protected]');
19+
20+
// Retrieve the hash code of a contact
21+
console.log(addressBook.hash('Ygritte'), '- Ygritte'); // 4
22+
console.log(addressBook.hash('Jonathan'), '- Jonathan'); // 5
23+
console.log(addressBook.hash('Jamie'), '- Jamie'); // 5
24+
console.log(addressBook.hash('Jack'), '- Jack'); // 7
25+
console.log(addressBook.hash('Jasmine'), '- Jasmine'); // 8
26+
console.log(addressBook.hash('Jake'), '- Jake'); // 9
27+
console.log(addressBook.hash('Nathan'), '- Nathan'); // 10
28+
console.log(addressBook.hash('Athelstan'), '- Athelstan'); // 7
29+
console.log(addressBook.hash('Sue'), '- Sue'); // 5
30+
console.log(addressBook.hash('Aethelwulf'), '- Aethelwulf'); // 5
31+
console.log(addressBook.hash('Sargeras'), '- Sargeras'); // 10
32+
33+
console.log(addressBook.toString());
34+
// {4 => ygritte@email.com}
35+
// {5 => aethelwulf@email.com}
36+
// {7 => athelstan@email.com}
37+
// {8 => jasmine@email.com}
38+
// {9 => jake@email.com}
39+
// {10 => sargeras@email.com}
40+
41+
42+
// to see the output of this file use the command: node src/08-dictionary-hash/05-hashmap-collision.js

src/08-dictionary-hash/dictionary.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// src/08-dictionary-hash/dictionary.js
2+
3+
class Dictionary {
4+
#items = {};
5+
#size = 0;
6+
7+
hasKey(key) {
8+
return this.#items[this.#elementToString(key)] != null;
9+
}
10+
11+
set(key, value) {
12+
if (key != null && value != null) {
13+
const tableKey = this.#elementToString(key);
14+
this.#items[tableKey] = value;
15+
this.#size++;
16+
return true;
17+
}
18+
return false;
19+
}
20+
21+
delete(key) {
22+
if (this.hasKey(key)) {
23+
delete this.#items[this.#elementToString(key)];
24+
this.#size--;
25+
return true;
26+
}
27+
return false;
28+
}
29+
30+
get(key) {
31+
return this.#items[this.#elementToString(key)];
32+
}
33+
34+
values() {
35+
return Object.values(this.#items);
36+
}
37+
38+
keys() {
39+
return Object.keys(this.#items);
40+
}
41+
42+
forEach(callbackFn) {
43+
for (const key in this.#items) {
44+
if (this.#items.hasOwnProperty(key)) {
45+
callbackFn(this.#items[key], key);
46+
}
47+
}
48+
}
49+
50+
#elementToString(data) {
51+
if (typeof data === 'object' && data !== null) {
52+
return JSON.stringify(data);
53+
} else {
54+
return data.toString();
55+
}
56+
}
57+
}
58+
59+
module.exports = Dictionary;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// src/08-dictionary-hash/hash-table-linear-probing.js
2+
3+
class HashTableLinearProbing {
4+
#table = [];
5+
6+
#loseLoseHashCode(key) {
7+
if (typeof key !== 'string') {
8+
key = this.#elementToString(key);
9+
}
10+
const calcASCIIValue = (acc, char) => acc + char.charCodeAt(0);
11+
const hash = key.split('').reduce((acc, char) => calcASCIIValue, 0);
12+
return hash % 37; // mod to reduce the hash code
13+
}
14+
15+
#djb2HashCode(key) {
16+
if (typeof key !== 'string') {
17+
key = this.#elementToString(key);
18+
}
19+
const calcASCIIValue = (acc, char) => (acc * 33) + char.charCodeAt(0);
20+
const hash = key.split('').reduce((acc, char) => calcASCIIValue, 5381);
21+
return hash % 1013;
22+
}
23+
24+
hash(key) {
25+
return this.#loseLoseHashCode(key);
26+
}
27+
28+
#elementToString(data) {
29+
if (typeof data === 'object' && data !== null) {
30+
return JSON.stringify(data);
31+
} else {
32+
return data.toString();
33+
}
34+
}
35+
36+
put(key, value) {
37+
if (key != null && value != null) {
38+
let index = this.hash(key);
39+
40+
// linear probing to find an empty slot
41+
while (this.#table[index] != null) {
42+
if (this.#table[index].key === key) {
43+
this.#table[index].value = value; // update existing key
44+
return true;
45+
}
46+
index++;
47+
index %= this.#table.length; // wrap around if end is reached
48+
}
49+
50+
this.#table[index] = {key, value};
51+
return true;
52+
}
53+
return false;
54+
}
55+
56+
get(key) {
57+
let index = this.hash(key);
58+
59+
// Linear probing to search for the key
60+
while (this.#table[index] != null) {
61+
if (this.#table[index].key === key) {
62+
return this.#table[index].value;
63+
}
64+
index++;
65+
index %= this.#table.length;
66+
}
67+
68+
return undefined; // key not found
69+
}
70+
71+
remove(key) {
72+
let index = this.hash(key);
73+
while (this.#table[index] != null) {
74+
if (this.#table[index].key === key) {
75+
delete this.#table[index];
76+
this.#verifyRemoveSideEffect(key, index);
77+
return true;
78+
}
79+
index++;
80+
index %= this.#table.length;
81+
}
82+
return false; // key not found
83+
}
84+
85+
#verifyRemoveSideEffect(key, removedPosition) {
86+
const size = this.#table.length;
87+
let index = removedPosition + 1;
88+
while (this.#table[index] != null) {
89+
const currentKey = this.#table[index].key;
90+
const currentHash = this.hash(currentKey);
91+
// check if the element should be repositioned
92+
if (currentHash <= removedPosition) {
93+
this.#table[removedPosition] = this.#table[index];
94+
delete this.#table[index];
95+
removedPosition = index;
96+
}
97+
index++;
98+
index %= size; // Wrap around if end is reached
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)