diff --git a/pkg/linkedlist/linkedlist.go b/pkg/linkedlist/linkedlist.go index 1ff9dce..e27dd82 100644 --- a/pkg/linkedlist/linkedlist.go +++ b/pkg/linkedlist/linkedlist.go @@ -6,58 +6,59 @@ package linkedlist import ( "bytes" + "fmt" "github.com/neelkshah/pandora/config" ) // LinkedList is the data structure used for the hash table. type LinkedList struct { - head *linkedListNode - tail *linkedListNode + head *Node + tail *Node count int } -// linkedListNode is the data structure that forms the LinkedList. -type linkedListNode struct { - values []valueNode - nextNode *linkedListNode +// Node is the data structure that forms the LinkedList. +type Node struct { + Values []valueNode + nextNode *Node } -// valueNode contains a single value to be stored in the LinkedList. +// ValueNode contains a single value to be stored in the LinkedList. type valueNode struct { - key []byte - value []byte + Key []byte + Value []byte } -// CreateLinkedList returns a pointer to a new empty linked list instance. -func CreateLinkedList() *LinkedList { +// createImpl returns a pointer to a new empty linked list instance. +func createImpl() *LinkedList { linkedList := LinkedList{head: nil, tail: nil, count: 0} return &linkedList } -// Append appends a given value to the end of the referenced LinkedList instance. -func (linkedList *LinkedList) Append(key []byte, value []byte) { - var newValue = valueNode{key: key, value: value} +// AppendImpl appends a given value to the end of the referenced LinkedList instance. +func (linkedList *LinkedList) appendImpl(key []byte, value []byte) { + var newValue = valueNode{Key: key, Value: value} if linkedList.count == 0 { - var newNode = linkedListNode{values: []valueNode{newValue}, nextNode: nil} + var newNode = Node{Values: []valueNode{newValue}, nextNode: nil} linkedList.head = &newNode linkedList.tail = &newNode linkedList.count = 1 return } var tailNode = linkedList.tail - if len(tailNode.values) < config.NODEFATNESS { - tailNode.values = append(tailNode.values, newValue) + if len(tailNode.Values) < config.NODEFATNESS { + tailNode.Values = append(tailNode.Values, newValue) } else { - var newNode = linkedListNode{values: []valueNode{newValue}, nextNode: nil} + var newNode = Node{Values: []valueNode{newValue}, nextNode: nil} tailNode.nextNode = &newNode linkedList.tail = &newNode } linkedList.count++ } -// Get returns the values associate with the key. -func (linkedList *LinkedList) Get(key []byte) ([][]byte, bool) { +// GetImpl returns the values associate with the key. +func (linkedList *LinkedList) getImpl(key []byte) ([][]byte, bool) { if linkedList == nil || linkedList.head == nil { return nil, true } @@ -65,23 +66,22 @@ func (linkedList *LinkedList) Get(key []byte) ([][]byte, bool) { var result = make([][]byte, 0) for { if currentNode == nil { - break + return result, len(result) != 0 } - for _, vnode := range (*currentNode).values { - if bytes.Equal(vnode.key, key) { - result = append(result, vnode.value) + for _, vnode := range (*currentNode).Values { + if bytes.Equal(vnode.Key, key) { + result = append(result, vnode.Value) } } currentNode = currentNode.nextNode } - return result, false } -// Delete deletes all key-value pairs having the key passed as parameter. -// It returns the number of deleted pairs and a bool indicating occurrence of an error. -func (linkedList *LinkedList) Delete(key []byte) (int, bool) { +// DeleteImpl deletes all key-value pairs having the key passed as parameter. +// It returns the number of deleted pairs and any error. +func (linkedList *LinkedList) deleteImpl(key []byte) (int, error) { if linkedList == nil || linkedList.head == nil { - return 0, true + return 0, fmt.Errorf("The linked list is empty") } var currentNode = linkedList.head var count = 0 @@ -90,16 +90,16 @@ func (linkedList *LinkedList) Delete(key []byte) (int, bool) { if currentNode == nil { break } - for _, vnode := range (*currentNode).values { - if !bytes.Equal(vnode.key, key) { - (*currentNode).values[k] = vnode + for _, vnode := range (*currentNode).Values { + if !bytes.Equal(vnode.Key, key) { + (*currentNode).Values[k] = vnode k++ continue } count++ } - (*currentNode).values = (*currentNode).values[:k] + (*currentNode).Values = (*currentNode).Values[:k] currentNode = currentNode.nextNode } - return count, false + return count, nil } diff --git a/pkg/linkedlist/linkedlist_api.go b/pkg/linkedlist/linkedlist_api.go new file mode 100644 index 0000000..901a131 --- /dev/null +++ b/pkg/linkedlist/linkedlist_api.go @@ -0,0 +1,60 @@ +// Author: Neel Shah, 2020 +// linkedlist_api.go provides the API for the CRUD methods for the linked-list ADT +// to be used as part of the hash table ADT. + +package linkedlist + +// CreateLinkedList returns a pointer to a new empty linked list instance. +func CreateLinkedList() *LinkedList { + return createImpl() +} + +// Append appends a key value pair to the linked list after checking their respective types. +func (linkedlist *LinkedList) Append(iKey interface{}, iValue interface{}) error { + + pair, conversionError := convert(iKey, iValue) + + if conversionError != nil { + return conversionError + } + + linkedlist.appendImpl(pair[0], pair[1]) + return nil +} + +// Get gets the value array of values associated with the passed key. +func (linkedlist *LinkedList) Get(iKey interface{}) ([][]byte, bool, error) { + + key, conversionError := convert(iKey) + + if conversionError != nil { + return nil, false, conversionError + } + + valueArray, found := linkedlist.getImpl(key[0]) + + return valueArray, found, nil +} + +// Delete deletes the key-value pairs associated with the passed key. It returns the count of deleted pairs and any error. +func (linkedlist *LinkedList) Delete(iKey interface{}) (int, error) { + key, conversionError := convert(iKey) + + if conversionError != nil { + return 0, conversionError + } + + deletedCount, deletionError := linkedlist.deleteImpl(key[0]) + + return deletedCount, deletionError +} + +// Head returns the head node of the linked list. +func (linkedlist *LinkedList) Head() *Node { + return linkedlist.head +} + +// Next returns the next node in the linked list. +func (node *Node) Next() *Node { + return node.nextNode +} diff --git a/pkg/linkedlist/linkedlist_test.go b/pkg/linkedlist/linkedlist_test.go index 11369f2..0adc656 100644 --- a/pkg/linkedlist/linkedlist_test.go +++ b/pkg/linkedlist/linkedlist_test.go @@ -25,70 +25,80 @@ func TestCreateLinkedList(t *testing.T) { // Test append function for single value append. func TestAppend(t *testing.T) { var response = *CreateLinkedList() - response.Append([]byte("a"), helper.IntToByte(5)) - if response.head == nil || + appendError := response.Append("a", 5) + if appendError != nil || + response.head == nil || response.tail == nil || response.count != 1 || response.head != response.tail || - helper.ByteToInt(response.head.values[0].value) != 5 || - len(response.head.values) != 1 { - t.Fail() + helper.ByteToInt(response.head.Values[0].Value) != 5 || + len(response.head.Values) != 1 { + t.Fatalf("Failed to append single key-value pair to empty linked list.") } } // Test whether fatness is maintained during append. func TestFatness(t *testing.T) { - var response = CreateLinkedList() + var list = CreateLinkedList() var N = 3 * config.NODEFATNESS for i := 1; i <= N; i++ { - var key = helper.IntToByte(rand.Uint64()) - var value = helper.IntToByte(rand.Uint64()) - response.Append(key, value) + var key = rand.Uint64() + var value = rand.Uint64() + if appendError := list.Append(key, value); appendError != nil { + t.Fatalf("Error in appending key-value pair %v, %v to linkedlist.\nInner error: %v", key, value, appendError) + } } - var responselist = *response + var responselist = *list if (responselist).count != 3*config.NODEFATNESS || responselist.head == responselist.tail || responselist.head == nil || responselist.tail == nil { - t.Fail() + t.Fatalf("Error in appending to fat nodes.") } var currentNode = *responselist.head + var i = 0 for { - if len(currentNode.values) != config.NODEFATNESS { - t.Fail() + if len(currentNode.Values) != config.NODEFATNESS { + t.Fatalf("Error in individual node fatness.") } + i++ if currentNode.nextNode == nil { - return + break } currentNode = *currentNode.nextNode } + if i != 3 { + t.Fatalf("Overall fatness not being maintained. Number of fat nodes != 3") + } } // Test get func TestGet(t *testing.T) { - var response = CreateLinkedList() - response.Append(helper.IntToByte(5), helper.IntToByte(7)) - if result, isEmpty := response.Get(helper.IntToByte(5)); isEmpty == true || !bytes.Equal(result[0], helper.IntToByte(7)) { - t.Fail() + var list = CreateLinkedList() + if appendError := list.Append(5, 7); appendError != nil { + t.Fatalf("Error in appending key-value pair to linkedlist.\nInner error: %v", appendError) } - if result, isEmpty := response.Get(helper.IntToByte(7)); isEmpty == true || len(result) != 0 { - t.Fail() + if result, found, _ := list.Get(5); !found || !bytes.Equal(result[0], helper.IntToByte(7)) { + t.Fatalf("Value present in linkedlist, but not found.") + } + if result, found, _ := list.Get(helper.IntToByte(7)); found || len(result) != 0 { + t.Fatalf("Value not present in linkedlist, returned as found.") } } // Test get behaviour for an empty LinkedList instance func TestGetNilValidation(t *testing.T) { var response = CreateLinkedList() - if result, isEmpty := response.Get(helper.IntToByte(5)); isEmpty == false || result != nil { - t.Fail() + if result, found, _ := response.Get(helper.IntToByte(5)); found || result != nil { + t.Fatalf("Getting values from empty linkedlist.") } } // Test delete behaviour for an empty LinkedList instance func TestDeleteNilValidation(t *testing.T) { var response = CreateLinkedList() - if count, isEmpty := response.Delete(helper.IntToByte(5)); isEmpty == false || count != 0 { - t.Fail() + if count, errorValue := response.Delete(helper.IntToByte(5)); errorValue == nil || count != 0 { + t.Fatalf("Deleting values from empty linkedlist.") } } @@ -96,18 +106,22 @@ func TestDeleteNilValidation(t *testing.T) { func TestDelete(t *testing.T) { var response = CreateLinkedList() for i := 1; i <= 5; i++ { - var key = helper.IntToByte(rand.Uint64()) - var value = helper.IntToByte(rand.Uint64()) - response.Append(key, value) + var key = rand.Uint64() + var value = rand.Uint64() + if appendResponse := response.Append(key, value); appendResponse != nil { + t.Fatalf("Error in appending key-value pair to linkedlist.") + } } - response.Append(helper.IntToByte(5), helper.IntToByte(7)) + response.Append(5, 7) for i := 1; i <= 5; i++ { - var key = helper.IntToByte(rand.Uint64()) - var value = helper.IntToByte(rand.Uint64()) - response.Append(key, value) + var key = rand.Uint64() + var value = rand.Uint64() + if appendResponse := response.Append(key, value); appendResponse != nil { + t.Fatalf("Error in appending key-value pair to linkedlist.") + } } - if count, status := response.Delete(helper.IntToByte(5)); count < 1 || status == true { - t.Fail() + if count, errorValue := response.Delete(5); count < 1 || errorValue != nil { + t.Fatalf("Error in deleting key-value pair.") } } diff --git a/pkg/linkedlist/utils.go b/pkg/linkedlist/utils.go index db7fdd2..f2e3408 100644 --- a/pkg/linkedlist/utils.go +++ b/pkg/linkedlist/utils.go @@ -1,14 +1,18 @@ package linkedlist -import "fmt" +import ( + "fmt" + + "github.com/neelkshah/pandora/pkg/helper" +) // Print prints the contents of a LinkedList instance. func (linkedList *LinkedList) Print() { fmt.Printf("Count of elements: %v\n", linkedList.count) var currentNode = *linkedList.head for { - for _, element := range currentNode.values { - fmt.Printf("%v, %v\t", element.key, element.value) + for _, element := range currentNode.Values { + fmt.Printf("%v, %v\t", element.Key, element.Value) } if currentNode.nextNode == nil { return @@ -17,3 +21,23 @@ func (linkedList *LinkedList) Print() { fmt.Println() } } + +// convert converts the given slice of values into a slice of byte slices. +func convert(originalValues ...interface{}) ([][]byte, error) { + var values [][]byte + + for _, originalValue := range originalValues { + switch keyType := originalValue.(type) { + case int: + values = append(values, helper.IntToByte(uint64(originalValue.(int)))) + case uint64: + values = append(values, helper.IntToByte(originalValue.(uint64))) + case string: + values = append(values, []byte(originalValue.(string))) + default: + return nil, fmt.Errorf("%v is of invalid type %v", originalValue, keyType) + } + } + + return values, nil +} diff --git a/test/linkedlist_test.go b/test/linkedlist_test.go index c7a50d4..7d994ea 100644 --- a/test/linkedlist_test.go +++ b/test/linkedlist_test.go @@ -5,6 +5,7 @@ package test import ( + "bytes" "math/rand" "testing" @@ -15,7 +16,22 @@ import ( func TestCreateLinkedList(t *testing.T) { var response = linkedlist.CreateLinkedList() if response == nil { - t.Fail() + t.Fatalf("Create is failing") + } +} + +func TestNext(t *testing.T) { + var list = linkedlist.CreateLinkedList() + list.Append(5, 7) + list.Append(7, 5) + var v1 = helper.IntToByte(5) + var v2 = helper.IntToByte(7) + var headValues = list.Head().Values + if !bytes.Equal(headValues[0].Key, v1) || + !bytes.Equal(headValues[0].Value, v2) || + !bytes.Equal(headValues[1].Key, v2) || + !bytes.Equal(headValues[1].Value, v1) { + t.Fatalf("Next is failing") } }