From 4b25a6b1c643b8377797bff7cd5a55f6d4df0553 Mon Sep 17 00:00:00 2001 From: Fabio Scotto di Santolo Date: Thu, 5 Jan 2023 15:13:07 +0100 Subject: [PATCH] Implemented linked list data structure --- pkg/collection/linked_list.go | 226 ++++++++++++++++++++++++ pkg/collection/linked_list_test.go | 273 +++++++++++++++++++++++++++++ 2 files changed, 499 insertions(+) create mode 100644 pkg/collection/linked_list.go create mode 100644 pkg/collection/linked_list_test.go diff --git a/pkg/collection/linked_list.go b/pkg/collection/linked_list.go new file mode 100644 index 0000000..b7b65c6 --- /dev/null +++ b/pkg/collection/linked_list.go @@ -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() +} diff --git a/pkg/collection/linked_list_test.go b/pkg/collection/linked_list_test.go new file mode 100644 index 0000000..521e81f --- /dev/null +++ b/pkg/collection/linked_list_test.go @@ -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 +}