Implemented linked list data structure
This commit is contained in:
226
pkg/collection/linked_list.go
Normal file
226
pkg/collection/linked_list.go
Normal 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()
|
||||||
|
}
|
||||||
273
pkg/collection/linked_list_test.go
Normal file
273
pkg/collection/linked_list_test.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user