Skip to content

Commit a25360f

Browse files
authored
linkedlist implementation (#7)
* adding gitignore * initial structure * added test and src dirs * Implemented CRUD methods linkedList Closing #3 * increasing coverage * update gitignore
1 parent 890b95f commit a25360f

File tree

10 files changed

+320
-31
lines changed

10 files changed

+320
-31
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@
1212
*.out
1313

1414
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
15-
.glide/
15+
.glide/
16+
17+
# Dev runner files
18+
cmd/runner/*

cmd/runner/main.go

Lines changed: 0 additions & 13 deletions
This file was deleted.

config/linkedlist_config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Author: Neel Shah, 2020
2+
// linkedlist_config.go contains the config variables for the linked-list ADT
3+
// to be used as part of the hash table ADT.
4+
5+
package config
6+
7+
// NODEFATNESS determines the number of valueNode instances contained in a single linkedListNode instance.
8+
var NODEFATNESS = 512

docs/meta2.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
### Little pointers
2+
3+
* Create a subdirectory under `pkg` for each new package.
4+
* Visibility and scope are based on packages as a unit, so organize code accordingly.
5+
* Split a package into files for readability and maintainability.
6+
* Identifiers having capitalized names are visible outside the package. This can be as low-grained as individual fields in a `struct`.
7+
* All global variables and configurations will go into their respective files in the `config` package.
8+
* In Go, tests are not different from regular code. Rules of visibility apply to test code too. So low-level unit tests are present in a separate file within the package itself.
9+
* End-to-end test cases can be placed in a separate `test` package. This will help organize test code better, and make profiling and benchmarking easier; whatever profiling has to be done can be done within the `test` package.
10+
11+
### A primer for writing unit tests
12+
13+
(Check the `pkg\linkedlist\linkedlist_test.go` file for reference)
14+
* Name the file ending in `_test.go`
15+
* Import the `testing` package.
16+
* Write a separate function for each test.
17+
* Unit tests follow the arrange, act, assert layout. First, arrange all the local variables needed (parameters, connectors, configs, etc). Then execute the action that is being tested. Finally, assert the correctness of the outcome. Don't forget to clean up after a test (file descriptors, slices, connectors, etc).
18+
* Assertion of correctness has to be complete - for example, in the case of the linkedlist, we have verified the number of elements, null fields, the actual values and so on.
19+
* In VS Code, you can check code coverage by pressing `Ctrl+Shift+P` and selecting `Go: Toggle test coverage in current package`. It highlights the code by test coverage. Please make sure you cover as much code as possible. This includes constructors and CRUD methods especially, since those are often the source of memory leaks and null errors and the like. Print functions are difficult to cover and can be avoided.

pkg/helper/converter.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package helper
2+
3+
import "encoding/binary"
4+
5+
// IntToByte takes a uint64 value and converts it into a little endian byte array.
6+
func IntToByte(value uint64) []byte {
7+
arr := make([]byte, 8)
8+
binary.LittleEndian.PutUint64(arr, value)
9+
return arr
10+
}
11+
12+
// ByteToInt takes a little endian byte array and converts it into a uint64 value.
13+
func ByteToInt(arr []byte) uint64 {
14+
value := binary.LittleEndian.Uint64(arr[0:])
15+
return value
16+
}

pkg/linkedlist/linkedlist.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Author: Neel Shah, 2020
2+
// linkedlist.go contains the CRUD methods for the linked-list ADT
3+
// to be used as part of the hash table ADT.
4+
5+
package linkedlist
6+
7+
import (
8+
"bytes"
9+
10+
"github.com/neelkshah/pandora/config"
11+
)
12+
13+
// LinkedList is the data structure used for the hash table.
14+
type LinkedList struct {
15+
head *linkedListNode
16+
tail *linkedListNode
17+
count int
18+
}
19+
20+
// linkedListNode is the data structure that forms the LinkedList.
21+
type linkedListNode struct {
22+
values []valueNode
23+
nextNode *linkedListNode
24+
}
25+
26+
// valueNode contains a single value to be stored in the LinkedList.
27+
type valueNode struct {
28+
key []byte
29+
value []byte
30+
}
31+
32+
// CreateLinkedList returns a pointer to a new empty linked list instance.
33+
func CreateLinkedList() *LinkedList {
34+
linkedList := LinkedList{head: nil, tail: nil, count: 0}
35+
return &linkedList
36+
}
37+
38+
// Append appends a given value to the end of the referenced LinkedList instance.
39+
func (linkedList *LinkedList) Append(key []byte, value []byte) {
40+
var newValue = valueNode{key: key, value: value}
41+
if linkedList.count == 0 {
42+
var newNode = linkedListNode{values: []valueNode{newValue}, nextNode: nil}
43+
linkedList.head = &newNode
44+
linkedList.tail = &newNode
45+
linkedList.count = 1
46+
return
47+
}
48+
var tailNode = linkedList.tail
49+
if len(tailNode.values) < config.NODEFATNESS {
50+
tailNode.values = append(tailNode.values, newValue)
51+
} else {
52+
var newNode = linkedListNode{values: []valueNode{newValue}, nextNode: nil}
53+
tailNode.nextNode = &newNode
54+
linkedList.tail = &newNode
55+
}
56+
linkedList.count++
57+
}
58+
59+
// Get returns the values associate with the key.
60+
func (linkedList *LinkedList) Get(key []byte) ([][]byte, bool) {
61+
if linkedList == nil || linkedList.head == nil {
62+
return nil, true
63+
}
64+
var currentNode = linkedList.head
65+
var result = make([][]byte, 0)
66+
for {
67+
if currentNode == nil {
68+
break
69+
}
70+
for _, vnode := range (*currentNode).values {
71+
if bytes.Equal(vnode.key, key) {
72+
result = append(result, vnode.value)
73+
}
74+
}
75+
currentNode = currentNode.nextNode
76+
}
77+
return result, false
78+
}
79+
80+
// Delete deletes all key-value pairs having the key passed as parameter.
81+
// It returns the number of deleted pairs and a bool indicating occurrence of an error.
82+
func (linkedList *LinkedList) Delete(key []byte) (int, bool) {
83+
if linkedList == nil || linkedList.head == nil {
84+
return 0, true
85+
}
86+
var currentNode = linkedList.head
87+
var count = 0
88+
var k = 0
89+
for {
90+
if currentNode == nil {
91+
break
92+
}
93+
for _, vnode := range (*currentNode).values {
94+
if !bytes.Equal(vnode.key, key) {
95+
(*currentNode).values[k] = vnode
96+
k++
97+
continue
98+
}
99+
count++
100+
}
101+
(*currentNode).values = (*currentNode).values[:k]
102+
currentNode = currentNode.nextNode
103+
}
104+
return count, false
105+
}

pkg/linkedlist/linkedlist_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Author: Neel Shah, 2020
2+
// linkedlist_test.go contains unit tests for the CRUD methods for the linked-list ADT
3+
// to be used as part of the hash table ADT.
4+
5+
package linkedlist
6+
7+
import (
8+
"bytes"
9+
"math/rand"
10+
"testing"
11+
12+
"github.com/neelkshah/pandora/pkg/helper"
13+
14+
"github.com/neelkshah/pandora/config"
15+
)
16+
17+
// Test create function.
18+
func TestCreateLinkedList(t *testing.T) {
19+
var response = *CreateLinkedList()
20+
if response.head != nil || response.tail != nil || response.count != 0 {
21+
t.Fail()
22+
}
23+
}
24+
25+
// Test append function for single value append.
26+
func TestAppend(t *testing.T) {
27+
var response = *CreateLinkedList()
28+
response.Append([]byte("a"), helper.IntToByte(5))
29+
if response.head == nil ||
30+
response.tail == nil ||
31+
response.count != 1 ||
32+
response.head != response.tail ||
33+
helper.ByteToInt(response.head.values[0].value) != 5 ||
34+
len(response.head.values) != 1 {
35+
t.Fail()
36+
}
37+
}
38+
39+
// Test whether fatness is maintained during append.
40+
func TestFatness(t *testing.T) {
41+
var response = CreateLinkedList()
42+
var N = 3 * config.NODEFATNESS
43+
for i := 1; i <= N; i++ {
44+
var key = helper.IntToByte(rand.Uint64())
45+
var value = helper.IntToByte(rand.Uint64())
46+
response.Append(key, value)
47+
}
48+
var responselist = *response
49+
if (responselist).count != 3*config.NODEFATNESS ||
50+
responselist.head == responselist.tail ||
51+
responselist.head == nil ||
52+
responselist.tail == nil {
53+
t.Fail()
54+
}
55+
var currentNode = *responselist.head
56+
for {
57+
if len(currentNode.values) != config.NODEFATNESS {
58+
t.Fail()
59+
}
60+
if currentNode.nextNode == nil {
61+
return
62+
}
63+
currentNode = *currentNode.nextNode
64+
}
65+
}
66+
67+
// Test get
68+
func TestGet(t *testing.T) {
69+
var response = CreateLinkedList()
70+
response.Append(helper.IntToByte(5), helper.IntToByte(7))
71+
if result, isEmpty := response.Get(helper.IntToByte(5)); isEmpty == true || !bytes.Equal(result[0], helper.IntToByte(7)) {
72+
t.Fail()
73+
}
74+
if result, isEmpty := response.Get(helper.IntToByte(7)); isEmpty == true || len(result) != 0 {
75+
t.Fail()
76+
}
77+
}
78+
79+
// Test get behaviour for an empty LinkedList instance
80+
func TestGetNilValidation(t *testing.T) {
81+
var response = CreateLinkedList()
82+
if result, isEmpty := response.Get(helper.IntToByte(5)); isEmpty == false || result != nil {
83+
t.Fail()
84+
}
85+
}
86+
87+
// Test delete behaviour for an empty LinkedList instance
88+
func TestDeleteNilValidation(t *testing.T) {
89+
var response = CreateLinkedList()
90+
if count, isEmpty := response.Delete(helper.IntToByte(5)); isEmpty == false || count != 0 {
91+
t.Fail()
92+
}
93+
}
94+
95+
// Test deletion
96+
func TestDelete(t *testing.T) {
97+
var response = CreateLinkedList()
98+
for i := 1; i <= 5; i++ {
99+
var key = helper.IntToByte(rand.Uint64())
100+
var value = helper.IntToByte(rand.Uint64())
101+
response.Append(key, value)
102+
}
103+
response.Append(helper.IntToByte(5), helper.IntToByte(7))
104+
for i := 1; i <= 5; i++ {
105+
var key = helper.IntToByte(rand.Uint64())
106+
var value = helper.IntToByte(rand.Uint64())
107+
response.Append(key, value)
108+
}
109+
110+
if count, status := response.Delete(helper.IntToByte(5)); count < 1 || status == true {
111+
t.Fail()
112+
}
113+
}

pkg/linkedlist/printer.go

Lines changed: 0 additions & 13 deletions
This file was deleted.

pkg/linkedlist/utils.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package linkedlist
2+
3+
import "fmt"
4+
5+
// Print prints the contents of a LinkedList instance.
6+
func (linkedList *LinkedList) Print() {
7+
fmt.Printf("Count of elements: %v\n", linkedList.count)
8+
var currentNode = *linkedList.head
9+
for {
10+
for _, element := range currentNode.values {
11+
fmt.Printf("%v, %v\t", element.key, element.value)
12+
}
13+
if currentNode.nextNode == nil {
14+
return
15+
}
16+
currentNode = *currentNode.nextNode
17+
fmt.Println()
18+
}
19+
}

test/linkedlist_test.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,47 @@
1+
// Author: Neel Shah, 2020
2+
// linkedlist_test.go contains E2E tests for the CRUD methods for the linked-list ADT
3+
// to be used as part of the hash table ADT.
4+
15
package test
26

37
import (
8+
"math/rand"
49
"testing"
510

11+
"github.com/neelkshah/pandora/pkg/helper"
612
"github.com/neelkshah/pandora/pkg/linkedlist"
713
)
814

9-
func TestFiver(t *testing.T) {
10-
var response = linkedlist.Fiver()
15+
func TestCreateLinkedList(t *testing.T) {
16+
var response = linkedlist.CreateLinkedList()
17+
if response == nil {
18+
t.Fail()
19+
}
20+
}
21+
22+
func BenchmarkAppend(b *testing.B) {
23+
var response = linkedlist.CreateLinkedList()
24+
for i := 0; i < b.N; i++ {
25+
var key = helper.IntToByte(rand.Uint64())
26+
var value = helper.IntToByte(rand.Uint64())
27+
response.Append(key, value)
28+
}
29+
}
30+
31+
func BenchmarkGet(b *testing.B) {
32+
var response = populatedList()
33+
b.ResetTimer()
34+
for i := 0; i < b.N; i++ {
35+
response.Get(helper.IntToByte(rand.Uint64()))
36+
}
37+
}
1138

12-
if response != 5 {
13-
t.Errorf("Expected value 5, got %v", response)
39+
func populatedList() *(linkedlist.LinkedList) {
40+
var response = linkedlist.CreateLinkedList()
41+
for i := 0; i < 10; i++ {
42+
var key = helper.IntToByte(rand.Uint64())
43+
var value = helper.IntToByte(rand.Uint64())
44+
response.Append(key, value)
1445
}
46+
return response
1547
}

0 commit comments

Comments
 (0)