Skip to content

Commit

Permalink
Add hashtable tests for Create, Insert, Delete
Browse files Browse the repository at this point in the history
Add comments and test case fail messages
  • Loading branch information
twitu committed Sep 23, 2020
1 parent beae6b6 commit 540368c
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 28 deletions.
2 changes: 1 addition & 1 deletion config/hashtable_config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package config

var (
HASHTABLE_GROW_FACTOR uint64 = 2
HASHTABLE_GROW_FACTOR float64 = 2.0
HASHTABLE_SHRINK_FACTOR float64 = 0.5
HASHTABLE_GROW_LIMIT uint64 = 1048576
HASHTABLE_SHRINK_LIMIT uint64 = 32
Expand Down
27 changes: 21 additions & 6 deletions pkg/hashtable/hashtable.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ type HashTable struct {
deletes uint64
}

func CreateHashTable(size uint64) *HashTable {
// Create hashtable with number of buckets given by size
func Create(size uint64) *HashTable {
var newHashTable = HashTable{table: make([]*ll.LinkedList, size), buckets: size, inserts: 0, deletes: 0}
for i := 0; i < int(size); i++ {
newHashTable.table[i] = ll.CreateLinkedList()
Expand All @@ -22,26 +23,33 @@ func CreateHashTable(size uint64) *HashTable {
return &newHashTable
}

// Insert value into hashtable
func (ht *HashTable) Insert(key uint64, value uint64) {
keyBytes := helper.IntToByte(key)
hashKey := helper.HashKey(keyBytes, ht.buckets)
valueBytes := helper.IntToByte(value)
bucket := ht.table[hashKey]

bucket.Append(keyBytes, valueBytes)
ht.inserts += 1
ht.inserts++
}

// Delete all values for the given key from
// hashtable. Return number of values deleted
// and true if no values were deleted
func (ht *HashTable) Delete(key uint64) (int, bool) {
keyBytes := helper.IntToByte(key)
hashKey := helper.HashKey(keyBytes, ht.buckets)
bucket := ht.table[hashKey]
count, status := bucket.Delete(keyBytes)
ht.deletes -= uint64(count)
ht.deletes += uint64(count)

return count, status
}

// Get all the values for the given key stored
// in the table. Return array of values and status
// true if there are no value to return
func (ht *HashTable) Get(key uint64) ([]uint64, bool) {
keyBytes := helper.IntToByte(key)
hashKey := helper.HashKey(keyBytes, ht.buckets)
Expand All @@ -57,8 +65,10 @@ func (ht *HashTable) Get(key uint64) ([]uint64, bool) {
return objects, status
}

// Grow grows the current hashtable by a constant factor
// it returns reference to new hashtable
func (ht *HashTable) Grow() *HashTable {
var newBuckets = uint64(ht.buckets * 2)
var newBuckets uint64 = uint64(float64(ht.buckets) * config.HASHTABLE_GROW_FACTOR)

// Do note shrink below limit
if newBuckets <= config.HASHTABLE_GROW_LIMIT {
Expand All @@ -68,8 +78,10 @@ func (ht *HashTable) Grow() *HashTable {
}
}

// Shrink shrinks the current hashtable by a constant factor
// it returns reference to new hashtable
func (ht *HashTable) Shrink() *HashTable {
var newBuckets = uint64(ht.buckets / 2)
var newBuckets uint64 = uint64(float64(ht.buckets) * config.HASHTABLE_SHRINK_FACTOR)

// Do note shrink below limit
if newBuckets <= config.HASHTABLE_SHRINK_LIMIT {
Expand All @@ -79,8 +91,11 @@ func (ht *HashTable) Shrink() *HashTable {
}
}

// Resize given hashtable to new hashtable of given size
// copies all values from existing hash table to new hashtable
// returns reference to new hashtable
func (ht *HashTable) Resize(size uint64) *HashTable {
var newHt = CreateHashTable(size)
var newHt = Create(size)

// iterate over all values and insert into new hash table

Expand Down
97 changes: 76 additions & 21 deletions pkg/hashtable/hashtable_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package hashtable

import (
"bytes"
"testing"

"github.com/neelkshah/pandora/pkg/helper"

"github.com/neelkshah/pandora/config"
)

func TestCreateHashTable(t *testing.T) {
var response = *CreateHashTable()
if response.buckets != uint32(config.HASHTABLE_INIT_SIZE) || response.inserts != 0 || response.deletes != 0 {
func TestCreate(t *testing.T) {
var response = *Create(10)
if response.buckets != 10 || response.inserts != 0 || response.deletes != 0 {
t.Fatalf("Fail at values")
}

Expand All @@ -22,32 +21,88 @@ func TestCreateHashTable(t *testing.T) {
}

func TestInsert(t *testing.T) {
// Create and insert value in hashtable
var key, value uint64 = 5, 10
var table = *CreateHashTable()
var hashTable = *Create(10)
hashTable.Insert(5, 10)

// checked linkedlist for successful insertion
var keyBytes = helper.IntToByte(uint64(key))
var hashKey = helper.HashKey(keyBytes, table.buckets)
var hashKey = helper.HashKey(keyBytes, hashTable.buckets)
valueBytes := helper.IntToByte(value)
bucket := table.table[hashKey]
bucket := hashTable.table[hashKey]
response, status := bucket.Get(keyBytes)

// checks
// TODO: A better way to map tests to error messages?
if status {
t.Fatalf("LinkedList does not contain anything insertion failed")
}

table.Insert(5, 10)
if len(response) != 1 {
t.Fatalf("Linkedlist should contain only one value after single insert")
}

if bytes.Compare(response[0], valueBytes) != 0 {
t.Fatalf("Hashtable did not store correct value")
}

// TODO: how to check if number is inserted
// check linkedlist using its APIs
// or retrieive it from linkedlist and check
if hashTable.inserts != 1 {
t.Fatalf("Insert count failed")
}
}

func TestGet(t *testing.T) {
// create hashtable and insert value into underlying linkedlist
var key, value, size uint64 = 5, 10, 10
var table = *CreateHashTable(size)
var keyBytes = helper.IntToByte(uint64(key))
var hashKey = helper.HashKey(keyBytes, table.buckets)
var hashTable = *Create(size)
var keyBytes = helper.IntToByte(key)
var hashKey = helper.HashKey(keyBytes, hashTable.buckets)
valueBytes := helper.IntToByte(value)
bucket := table.table[hashKey]
bucket := hashTable.table[hashKey]
bucket.Append(keyBytes, valueBytes)

// get value from linkedlist
var response, status = hashTable.Get(key)

table.Insert(5, 10)
// checks
// TODO: A better way to map tests to error messages?
if status {
t.Fatalf("Get failed")
}

if len(response) != 1 {
t.Fatalf("Get should return a single value. It returned %d values.", len(response))
}

// TODO: how to test get
// insert using linkedlist API
// or directly insert into hashtable using insert
// but then that's not unit testing
if response[0] != value {
t.Fatalf("Get did not return the correct value. Value should be %d is %d", value, response[0])
}
}

func TestDelete(t *testing.T) {
// create hashtable and insert value into underlying linkedlist
var key, value, size uint64 = 5, 10, 10
var hashTable = *Create(size)
var keyBytes = helper.IntToByte(key)
var hashKey = helper.HashKey(keyBytes, hashTable.buckets)
valueBytes := helper.IntToByte(value)
bucket := hashTable.table[hashKey]
bucket.Append(keyBytes, valueBytes)

// delete value from hash table and check underlying linkedlist
var response, status = hashTable.Delete(key)

// checks
if status {
t.Fatalf("Delete failed")
}

if response != 1 {
t.Fatalf("Deleted %d values should have deleted 1 value", response)
}

if hashTable.deletes != 1 {
t.Fatalf("Delete counter failed. It counted %d but should have counted 1", hashTable.deletes)
}
}

0 comments on commit 540368c

Please sign in to comment.