Implemented linked list data structure

This commit is contained in:
Fabio Scotto di Santolo
2023-01-05 15:13:07 +01:00
parent 61fd55fb5c
commit 4b25a6b1c6
2 changed files with 499 additions and 0 deletions

View File

@@ -0,0 +1,226 @@
package collection
import (
"fmt"
"log"
"strings"
)
var (
ErrPositionNegative = fmt.Errorf("position can not be negative")
ErrEmptyList = fmt.Errorf("no nodes in list")
ErrNodeNotFound = fmt.Errorf("node not found")
)
type ErrIndexOutOfBound struct {
index int
size int
}
func (e ErrIndexOutOfBound) Error() string {
return fmt.Sprintf("index %d out of bound from range %d and %d", e.index, 0, e.size-1)
}
type node[E any] struct {
value E
prev *node[E]
next *node[E]
}
type listIterator[E any] struct {
currentNode *node[E]
currentIndex int
}
func (it *listIterator[E]) HasNext() bool {
return it.currentNode != nil
}
func (it *listIterator[E]) Next() E {
curr := it.currentNode
it.currentNode = curr.next
it.currentIndex++
return curr.value
}
func (it *listIterator[E]) NextWithIndex() (int, E) {
curr := it.currentNode
index := it.currentIndex
it.currentNode = curr.next
it.currentIndex++
return index, curr.value
}
type emptyListIterator[E any] struct{}
func (it *emptyListIterator[E]) HasNext() bool {
return false
}
func (it *emptyListIterator[E]) Next() E {
return *new(E)
}
func (it *emptyListIterator[E]) NextWithIndex() (int, E) {
return 0, *new(E)
}
type LinkedList[E any] struct {
head *node[E]
size int
}
func (l *LinkedList[E]) Iterator() Iterator[E] {
if l.Empty() {
return &emptyListIterator[E]{}
}
return &listIterator[E]{l.head, 0}
}
func (l *LinkedList[E]) Empty() bool {
return l.head == nil
}
func (l *LinkedList[E]) Size() int {
return l.size
}
func (l *LinkedList[E]) Back() (E, error) {
return l.GetAt(l.size - 1)
}
func (l *LinkedList[E]) Front() (E, error) {
return l.GetAt(0)
}
func (l *LinkedList[E]) GetAt(pos int) (E, error) {
node, err := l.findNode(pos)
if err != nil {
return *new(E), err
}
return node.value, nil
}
// findNode returns node at given position from linked list
func (l *LinkedList[E]) findNode(pos int) (*node[E], error) {
if l.Empty() {
return nil, ErrEmptyList
}
ptr := l.head
if pos < 0 {
return nil, ErrPositionNegative
}
if pos > (l.size - 1) {
return nil, ErrIndexOutOfBound{pos, l.size}
}
for i := 0; i < pos; i++ {
ptr = ptr.next
}
return ptr, nil
}
func (l *LinkedList[E]) PushFront(item E) {
l.PushAt(item, 0)
}
func (l *LinkedList[E]) PushBack(item E) {
l.PushAt(item, l.size)
}
// PushAt inserts new node at given position
func (l *LinkedList[E]) PushAt(item E, pos int) {
// create a new node
newNode := node[E]{value: item}
// validate the position
if pos < 0 {
return
}
if pos == 0 {
oldNode := l.head
newNode.next = oldNode
l.head = &newNode
if oldNode != nil {
oldNode.prev = l.head
}
l.size++
return
}
if pos > l.size {
return
}
n, _ := l.findNode(pos)
newNode.next = n
var prevNode *node[E]
if n != nil {
prevNode = n.prev
} else {
prevNode, _ = l.findNode(pos - 1)
}
prevNode.next = &newNode
newNode.prev = prevNode
l.size++
}
// DeleteAt deletes node at given position from linked list
func (l *LinkedList[E]) DeleteAt(pos int) error {
// validate the position
if pos < 0 {
log.Println("position can not be negative")
return ErrPositionNegative
}
if l.size == 0 {
log.Println("no nodes in list")
return ErrEmptyList
}
if pos == 0 {
// For first position not exists prev node
myNode, err := l.findNode(pos)
if err != nil {
return err
}
l.head = myNode.next
l.head.prev = nil
} else {
prevNode, _ := l.findNode(pos - 1)
if prevNode == nil {
log.Println("node not found")
return ErrNodeNotFound
}
myNode, err := l.findNode(pos)
if err != nil {
return err
}
prevNode.next = myNode.next
if myNode.next != nil {
myNode.next.prev = prevNode
}
}
l.size--
return nil
}
func (l *LinkedList[E]) String() string {
var sb strings.Builder
sb.WriteString("[")
for i := 0; i < l.Size(); i++ {
var s string
item, _ := l.GetAt(i)
if i >= l.Size()-1 {
s = fmt.Sprintf("%v", item)
} else {
s = fmt.Sprintf("%v, ", item)
}
sb.WriteString(s)
}
sb.WriteString("]")
return sb.String()
}

View File

@@ -0,0 +1,273 @@
package collection
import (
"testing"
)
func TestLinkedList_Empty(t *testing.T) {
useCases := []struct {
description string
list *LinkedList[int]
want bool
}{
{description: "no items in list", list: newList(), want: true},
{description: "one item in list", list: newList(1), want: false},
{description: "more items in list", list: newList(1, 2, 3), want: false},
}
for _, tt := range useCases {
result := tt.list.Empty()
if result != tt.want {
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
}
}
}
func TestLinkedList_Size(t *testing.T) {
useCases := []struct {
description string
list *LinkedList[int]
want int
}{
{description: "no items in list", list: newList(), want: 0},
{description: "one item in list", list: newList(1), want: 1},
{description: "more items in list", list: newList(1, 2, 3), want: 3},
}
for _, tt := range useCases {
result := tt.list.Size()
if result != tt.want {
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
}
}
}
func TestLinkedList_GetAt(t *testing.T) {
useCases := []struct {
description string
list *LinkedList[int]
pos int
err error
want int
}{
{description: "get first item", list: newList(1, 2, 3), pos: 0, want: 1, err: nil},
{description: "get middle item", list: newList(1, 2, 3), pos: 1, want: 2, err: nil},
{description: "get last item", list: newList(1, 2, 3), pos: 2, want: 3, err: nil},
{description: "get last item", list: newList(1, 2, 3), pos: 2, want: 3, err: nil},
{description: "no items in list return zero value of the type", list: newList(), pos: 0, want: 0, err: ErrEmptyList},
{description: "get item with negative position", list: newList(1, 2, 3), pos: -1, want: 0, err: ErrPositionNegative},
{description: "get item with index out of bound", list: newList(1, 2, 3), pos: 4, want: 0, err: ErrIndexOutOfBound{4, 3}},
}
for _, tt := range useCases {
result, err := tt.list.GetAt(tt.pos)
if result != tt.want && err != tt.err {
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
}
}
}
func TestLinkedList_Back(t *testing.T) {
useCases := []struct {
description string
list *LinkedList[int]
err error
want int
}{
{description: "no items in list return zero value of the type", list: newList(), want: 0, err: ErrEmptyList},
{description: "singleton list return first element", list: newList(1), want: 1},
{description: "get last item", list: newList(1, 2, 3), want: 3},
}
for _, tt := range useCases {
result, err := tt.list.Back()
if result != tt.want && err != tt.err {
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
}
}
}
func TestLinkedList_Front(t *testing.T) {
useCases := []struct {
description string
list *LinkedList[int]
err error
want int
}{
{description: "no items in list return zero value of the type", list: newList(), want: 0, err: ErrEmptyList},
{description: "singleton list return first element", list: newList(1), want: 1},
{description: "get first item", list: newList(1, 2, 3), want: 1},
}
for _, tt := range useCases {
result, err := tt.list.Front()
if result != tt.want && err != tt.err {
t.Errorf("test: %s want %v got %v", tt.description, tt.want, result)
}
}
}
func TestLinkedList_PushAt(t *testing.T) {
useCases := []struct {
description string
original *LinkedList[int]
modified *LinkedList[int]
pos int
item int
}{
{description: "add item in first position in empty list",
original: newList(),
modified: newList(1),
pos: 0,
item: 1},
{description: "add item in first position in full list",
original: newList(1, 2, 3),
modified: newList(0, 1, 2, 3),
pos: 0,
item: 0},
{description: "add item in middle position in full list",
original: newList(1, 2, 3),
modified: newList(1, 2, 0, 3),
pos: 2,
item: 0},
{description: "add item in last position in full list",
original: newList(1, 2, 3),
modified: newList(1, 2, 3, 0),
pos: 3,
item: 0},
}
for _, tt := range useCases {
tt.original.PushAt(tt.item, tt.pos)
if !compareLists(tt.original, tt.modified) {
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
}
}
}
func TestLinkedList_PushBack(t *testing.T) {
useCases := []struct {
description string
original *LinkedList[int]
modified *LinkedList[int]
pos int
item int
}{
{description: "add item in first position in empty list",
original: newList(),
modified: newList(1),
item: 1},
{description: "add item in singleton list",
original: newList(1),
modified: newList(1, 0),
item: 0},
{description: "add item in full list",
original: newList(1, 2, 3),
modified: newList(1, 2, 3, 0),
item: 0},
}
for _, tt := range useCases {
tt.original.PushBack(tt.item)
if !compareLists(tt.original, tt.modified) {
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
}
}
}
func TestLinkedList_PushFront(t *testing.T) {
useCases := []struct {
description string
original *LinkedList[int]
modified *LinkedList[int]
pos int
item int
}{
{description: "add item in first position in empty list",
original: newList(),
modified: newList(1),
item: 1},
{description: "add item in singleton list",
original: newList(1),
modified: newList(0, 1),
item: 0},
{description: "add item in full list",
original: newList(1, 2, 3),
modified: newList(0, 1, 2, 3),
item: 0},
}
for _, tt := range useCases {
tt.original.PushFront(tt.item)
if !compareLists(tt.original, tt.modified) {
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
}
}
}
func TestLinkedList_DeleteAt(t *testing.T) {
useCases := []struct {
description string
original *LinkedList[int]
modified *LinkedList[int]
pos int
err error
}{
{description: "delete item with negative position",
original: newList(),
modified: nil,
pos: -1,
err: ErrPositionNegative},
{description: "delete item in empty list",
original: newList(),
modified: nil,
pos: 0,
err: ErrEmptyList},
{description: "delete item in position not found",
original: newList(1, 2, 3),
modified: newList(),
pos: 5,
err: ErrNodeNotFound},
{description: "delete item in first position",
original: newList(1, 2, 3),
modified: newList(2, 3),
pos: 0,
err: nil},
{description: "delete item in middle position",
original: newList(1, 2, 3),
modified: newList(1, 3),
pos: 1,
err: nil},
{description: "delete item in last position",
original: newList(1, 2, 3),
modified: newList(1, 2),
pos: 2,
err: nil},
}
for _, tt := range useCases {
err := tt.original.DeleteAt(tt.pos)
if !compareLists(tt.original, tt.modified) && err != tt.err {
t.Errorf("test: %s want %v got %v", tt.description, tt.modified, tt.original)
}
}
}
func newList(items ...int) *LinkedList[int] {
lst := &LinkedList[int]{}
for _, item := range items {
lst.PushBack(item)
}
return lst
}
func compareLists(lst1, lst2 *LinkedList[int]) bool {
for it := lst1.Iterator(); it.HasNext(); {
i, item1 := it.NextWithIndex()
item2, _ := lst2.GetAt(i)
if item1 != item2 {
return false
}
}
return true
}