Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[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

Open
miladsade96 wants to merge 5 commits into TheAlgorithms:master
base: master
Choose a base branch
Loading
from miladsade96:devHashTable
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions Data-Structures/HashTable/HashTable.js
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
export default class HashTable {
constructor(limit = 100) {
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The 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.)

limit is also a slightly misleading name: This is not a hard limit, just a hash table "size". If you insert more elements, you will just get worse performance as your buckets fill up.

// Initialize the storage and limit variables
this.storage = new Array(limit)
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to call this property this.#buckets (it should also be private).

this.limit = limit
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field is redundant with this.buckets.length.

}

// Hash function:
_hash(key, max) {
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the # syntax for private properties. The max parameter is also unnecessary; you can just get it as this.storage.length instead.

// 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]]
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use an object {key, value} here for better readability (s.t. instead of [0] and [1], you can do .key and .value respectively). You could consider introducing a class Entry or Pair that stores a key-value pair for this.

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++) {
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a for-of loop here: for (const entry of this.storage[index]).

if (this.storage[index][i][0] === key) {
// If the key exists, update the value
this.storage[index][i][1] = value
inserted = true
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just early return here. Then you don't need the inserted variable, and you will be more efficient (by not doing any further loop iterations).

}
}
// 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++) {
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a for-of loop.

// 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]) {
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use an early return here: const bucket = this.storage[index]; if (!bucket) return (empty / undefined bucket). Then you don't need to indent the rest of the function.

// If the key matches the key at the index and there is only one item in the bucket, delete the bucket
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The 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]
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The 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() {
Copy link
Collaborator

@appgurueu appgurueu Mar 2, 2024

Choose a reason for hiding this comment

The 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 JSON.stringify, work just as well for inspecting values.

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)
}
}
43 changes: 43 additions & 0 deletions Data-Structures/HashTable/test/HashTable.test.js
View file Open in desktop
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))
})
})
Loading

AltStyle によって変換されたページ (->オリジナル) /