-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
[DEV] Implementation of Hash Table Data Structure #1627
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1fdf350
4452716
0089b70
40dbe92
d05deeb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
export default class HashTable { | ||
constructor(limit = 100) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 100 seems very arbitrary. I'd force the user to choose for a fixed-size hash table. (I'd also make it clear that this is a fixed-size hash table (with dynamically grown buckets) as opposed to a dynamic hash table. In particular, a fixed-size hash table can't guarantee expected O(1) operations.)
|
||
// Initialize the storage and limit variables | ||
this.storage = new Array(limit) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be helpful to call this property |
||
this.limit = limit | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This field is redundant with |
||
} | ||
|
||
// Hash function: | ||
_hash(key, max) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the |
||
// Initialize the hash variable to 0 | ||
let hash = 0 | ||
// Iterate through the key and add the character code at each iteration to the hash variable | ||
for (let i = 0; i < key.length; i++) hash += key.charCodeAt(i) | ||
// Return the hash modulo the max | ||
return hash % max | ||
} | ||
|
||
// Insert a key-value pair into the hash table | ||
set(key, value) { | ||
// Hash the key | ||
const index = this._hash(key, this.limit) | ||
// If the index is empty, insert the key-value pair and create a bucket | ||
if (this.storage[index] === undefined) this.storage[index] = [[key, value]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use an object |
||
else { | ||
// If the index is not empty, iterate through the bucket (collision handling) | ||
let inserted = false | ||
for (let i = 0; i < this.storage[index].length; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a |
||
if (this.storage[index][i][0] === key) { | ||
// If the key exists, update the value | ||
this.storage[index][i][1] = value | ||
inserted = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just early |
||
} | ||
} | ||
// If the key does not exist, insert the key-value pair | ||
if (inserted === false) this.storage[index].push([key, value]) | ||
} | ||
} | ||
|
||
// Get a value from the hash table | ||
get(key) { | ||
// Hash the key | ||
const index = this._hash(key, this.limit) | ||
// If the index is empty, return undefined | ||
if (this.storage[index] === undefined) return undefined | ||
else { | ||
// If the index is not empty, iterate through the bucket | ||
for (let i = 0; i < this.storage[index].length; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a |
||
// If the key exists, return the value | ||
if (this.storage[index][i][0] === key) return this.storage[index][i][1] | ||
} | ||
} | ||
} | ||
|
||
// Remove a key-value pair from the hash table | ||
remove(key) { | ||
// Hash the key | ||
const index = this._hash(key, this.limit) | ||
// Check if the bucket exists | ||
if (this.storage[index]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd use an early return here: |
||
// If the key matches the key at the index and there is only one item in the bucket, delete the bucket | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (leaving an empty bucket behind wouldn't be an issue either; in fact i think the implementation may become slightly cleaner if you initialize all buckets to be empty, at a slight upfront performance cost at initialization) |
||
if ( | ||
this.storage[index].length === 1 && | ||
this.storage[index][0][0] === key | ||
) { | ||
delete this.storage[index] | ||
} else { | ||
// If the index is not empty, iterate through the bucket | ||
for (let i = 0; i < this.storage[index].length; i++) { | ||
// If the key exists, delete the key-value pair | ||
if (this.storage[index][i][0] === key) delete this.storage[index][i] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're creating an array with holes here. I think this is rather dirty; it results in suboptimal time complexities (deleted items negatively affecting the time it takes to search a bucket). It would be cleaner to just take an entry from the end, swap the entry to be deleted with that, then pop the entry from the end. |
||
} | ||
} | ||
} | ||
} | ||
|
||
// Check if a key exists in the hash table | ||
has(key) { | ||
// Hash the key to find the index | ||
const index = this._hash(key, this.limit) | ||
// Check if the bucket at the index exists | ||
if (this.storage[index]) { | ||
// Iterate through the bucket's key-value pairs | ||
for (let i = 0; i < this.storage[index].length; i++) { | ||
// Compare the current key with the target key and if the key is found, return true | ||
if (this.storage[index][i][0] === key) return true | ||
} | ||
} | ||
// If the key is not found, return false | ||
return false | ||
} | ||
|
||
// Print all keys/values in the table | ||
printTable() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think "print" helpers are good design. A debugger, or even just A better abstraction would be an "entries"-like method to iterate over all the key-value-pairs. |
||
for (let i = 0; i < this.storage.length; i++) { | ||
if (this.storage[i] !== undefined) | ||
console.log(`Bucket ${i}: ${JSON.stringify(this.storage[i])}`) | ||
else console.log(`Bucket ${i} Empty`) | ||
} | ||
} | ||
|
||
// Clear all key/values | ||
clear() { | ||
this.storage = new Array(this.limit) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import HashTable from '../HashTable.js' | ||
|
||
describe('HashTable Data Structure Tests', () => { | ||
test('HashTable Constructor', () => { | ||
const hashTable = new HashTable(10) | ||
expect(hashTable.limit).toBe(10) | ||
expect(hashTable.storage.length).toBe(10) | ||
}) | ||
|
||
test('HashTable get method', () => { | ||
const hashTable = new HashTable(10) | ||
expect(hashTable.get('key')).toBe(undefined) | ||
}) | ||
|
||
test('HashTable set method', () => { | ||
const hashTable = new HashTable(10) | ||
hashTable.set('js', 'javascript') | ||
expect(hashTable.get('js')).toBe('javascript') | ||
}) | ||
|
||
test('HashTable remove method', () => { | ||
const hashTable = new HashTable(10) | ||
hashTable.set('js', 'javascript') | ||
hashTable.remove('js') | ||
expect(hashTable.get('js')).toBe(undefined) | ||
}) | ||
|
||
test('HashTable has method', () => { | ||
const hashTable = new HashTable(10) | ||
hashTable.set('js', 'javascript') | ||
expect(hashTable.has('js')).toBe(true) | ||
}) | ||
|
||
test('HashTable clear method', () => { | ||
const hashTable = new HashTable(10) | ||
hashTable.set('js', 'javascript') | ||
hashTable.set('ts', 'typescript') | ||
hashTable.clear() | ||
expect(hashTable.get('js')).toBe(undefined) | ||
expect(hashTable.get('ts')).toBe(undefined) | ||
expect(hashTable.storage).toEqual(new Array(10)) | ||
}) | ||
}) |