From 52609916a88a6672473a82f14612d10fbcadc63f Mon Sep 17 00:00:00 2001 From: Sreekanth Date: Fri, 21 Oct 2022 10:09:15 +0530 Subject: [PATCH] Expose an interface for the stack package --- structure/stack/{stack_array.go => array.go} | 30 ++--- .../{stack_array_test.go => array_test.go} | 8 +- structure/stack/doubly_linked_list.go | 93 +++++++++++++ structure/stack/doubly_linked_list_test.go | 66 ++++++++++ structure/stack/linked_list.go | 83 ++++++++++++ structure/stack/linked_list_test.go | 51 +++++++ structure/stack/stack.go | 33 +++++ structure/stack/stack_test.go | 124 ------------------ structure/stack/stacklinkedlist.go | 72 ---------- structure/stack/stacklinkedlistwithlist.go | 60 --------- 10 files changed, 340 insertions(+), 280 deletions(-) rename structure/stack/{stack_array.go => array.go} (80%) rename structure/stack/{stack_array_test.go => array_test.go} (80%) create mode 100644 structure/stack/doubly_linked_list.go create mode 100644 structure/stack/doubly_linked_list_test.go create mode 100644 structure/stack/linked_list.go create mode 100644 structure/stack/linked_list_test.go create mode 100644 structure/stack/stack.go delete mode 100644 structure/stack/stack_test.go delete mode 100644 structure/stack/stacklinkedlist.go delete mode 100644 structure/stack/stacklinkedlistwithlist.go diff --git a/structure/stack/stack_array.go b/structure/stack/array.go similarity index 80% rename from structure/stack/stack_array.go rename to structure/stack/array.go index 7d69d65254..0a4a7a52b5 100644 --- a/structure/stack/stack_array.go +++ b/structure/stack/array.go @@ -11,24 +11,22 @@ import "errors" var ErrStackEmpty = errors.New("stack is empty") -/* -The methods can also be implemented directly on the slice. -``` -type Array[T any] []T -``` -However, this exposes the underlaying storage (slice) outside the package. -A struct is used instead, so that the underlying storage is not accessible outside the package. -*/ - // Array is an implementation of stack with slice as underlying storage. // ``` // stack := stack.NewArray[int]() // ``` +// Note that the type `Array` could also be implemented directly using a slice. +// ``` +// type Array[T any] []T +// ``` +// However, this exposes the underlying storage (slice) outside the package. +// A struct is used instead, so that the underlying storage is not accessible +// outside the package. type Array[T any] struct { store []T } -func NewArray[T any]() *Array[T] { +func NewArray[T any]() Interface[T] { return new(Array[T]) } @@ -59,7 +57,7 @@ func (s *Array[T]) Empty() bool { return s.Len() == 0 } -// Pop returns last inserted element and removes it from the underlaying storage +// Pop returns last inserted element and removes it from the underlying storage // If the stack is empty, ErrStackEmpty error is returned func (s *Array[T]) Pop() (T, error) { var element T @@ -80,10 +78,8 @@ func (s *Array[T]) Clear() { s.store = s.store[:0] } -// Truncate removes all elements and underlaying storage -func (s *Array[T]) Truncate() { - if s == nil { - return - } - s.store = nil +func (s *Array[T]) ToSlice() []T { + out := make([]T, len(s.store)) + copy(out, s.store) + return out } diff --git a/structure/stack/stack_array_test.go b/structure/stack/array_test.go similarity index 80% rename from structure/stack/stack_array_test.go rename to structure/stack/array_test.go index 499a09625e..3e50f53a30 100644 --- a/structure/stack/stack_array_test.go +++ b/structure/stack/array_test.go @@ -40,12 +40,6 @@ func Test_StackArray(t *testing.T) { stack.Clear() if stack.Len() != 0 && !stack.Empty() { - t.Errorf("Expected stack to be emtpy after Clear. Got len=%d, empty=%t", stack.Len(), stack.Empty()) - } - - stack.Truncate() - storeCapacity := cap(stack.store) - if storeCapacity != 0 { - t.Errorf("Expected store capacity to be zero after truncate. Got capacity=%d", storeCapacity) + t.Errorf("Expected stack to be empty after Clear. Got len=%d, empty=%t", stack.Len(), stack.Empty()) } } diff --git a/structure/stack/doubly_linked_list.go b/structure/stack/doubly_linked_list.go new file mode 100644 index 0000000000..a1724ea714 --- /dev/null +++ b/structure/stack/doubly_linked_list.go @@ -0,0 +1,93 @@ +package stack + +import ( + "container/list" +) + +// doublyLinkedList is an implementation of stack.Interface using the doubly linked list provided by `container/list` as its underlying storage. +type doublyLinkedList[T any] struct { + stack *list.List +} + +func NewDoublyLinkedList[T any]() Interface[T] { + return &doublyLinkedList[T]{ + stack: list.New(), + } +} + +// Push add a value into our stack +func (dl *doublyLinkedList[T]) Push(val T) { + dl.stack.PushFront(val) +} + +// Peek return last inserted element(top of the stack) without removing it from the stack +// If the stack is empty, ErrStackEmpty error is returned +func (dl *doublyLinkedList[T]) Peek() (T, error) { + var result T + if dl.Empty() { + return result, ErrStackEmpty + } + + element := dl.stack.Front() + if element == nil { + return result, ErrStackEmpty + } + + result = element.Value.(T) + return result, nil +} + +// Pop is return last value that insert into our stack +// also it will remove it in our stack +func (dl *doublyLinkedList[T]) Pop() (T, error) { + var result T + if dl.Empty() { + return result, ErrStackEmpty + } + + element := dl.stack.Front() + if element == nil { + return result, ErrStackEmpty + } + + dl.stack.Remove(element) + result = element.Value.(T) + return result, nil +} + +// Length returns the number of elements in the stack +func (dl *doublyLinkedList[T]) Len() int { + if dl == nil { + return 0 + } + return dl.stack.Len() +} + +// Empty returns true if stack has no elements and false otherwise. +func (dl *doublyLinkedList[T]) Empty() bool { + if dl == nil { + return true + } + return dl.stack.Len() == 0 +} + +// Clear initializes the underlying storage with a new empty doubly linked list, thus clearing the underlying storage. +func (dl *doublyLinkedList[T]) Clear() { + if dl == nil { + return + } + dl.stack = list.New() +} + +// ToSlice returns the elements of stack as a slice +func (dl *doublyLinkedList[T]) ToSlice() []T { + var result []T + if dl == nil { + return result + } + + for e := dl.stack.Front(); e != nil; e = e.Next() { + result = append(result, e.Value.(T)) + } + return result +} diff --git a/structure/stack/doubly_linked_list_test.go b/structure/stack/doubly_linked_list_test.go new file mode 100644 index 0000000000..954de3ca7c --- /dev/null +++ b/structure/stack/doubly_linked_list_test.go @@ -0,0 +1,66 @@ +// Stack Test +// description: based on `geeksforgeeks` description Stack is a linear data structure which follows a particular order in which the operations are performed. +// The order may be LIFO(Last In First Out) or FILO(First In Last Out). +// details: +// Stack Data Structure : https://www.geeksforgeeks.org/stack-data-structure-introduction-program/ +// Stack (abstract data type) : https://en.wikipedia.org/wiki/Stack_(abstract_data_type) +// author [Milad](https://github.com/miraddo) +// see stackarray.go, stacklinkedlist.go, stacklinkedlistwithlist.go + +package stack + +import ( + "testing" +) + +// TestStackLinkedListWithList for testing Stack with Container/List Library (STL) +func TestStackLinkedListWithList(t *testing.T) { + st := NewDoublyLinkedList[int]() + + t.Run("Stack Push", func(t *testing.T) { + + st.Push(2) + st.Push(3) + + if st.Len() != 2 { + t.Errorf("Expected 2 elements in the stack, found %d", st.Len()) + } + }) + + t.Run("Stack Pop", func(t *testing.T) { + pop, _ := st.Pop() + + if pop != 3 { + t.Errorf("Expected 3 from Pop operation, got %d", pop) + } + + if st.Len() != 1 { + t.Errorf("Expected stack length to be 1 after Pop operation, got %d", st.Len()) + } + }) + + t.Run("Stack Peek", func(t *testing.T) { + st.Push(2) + st.Push(83) + peek, _ := st.Peek() + if peek != 83 { + t.Errorf("Expected value 83 from Peek operation, got %d", peek) + } + }) + + t.Run("Stack Len", func(t *testing.T) { + if st.Len() != 3 { + t.Errorf("Expected stack length to be 3, got %d", st.Len()) + } + }) + + t.Run("Stack Empty", func(t *testing.T) { + if st.Empty() { + t.Error("Stack should not be empty") + } + st.Clear() + if !st.Empty() { + t.Error("Stack is expected to be empty") + } + }) +} diff --git a/structure/stack/linked_list.go b/structure/stack/linked_list.go new file mode 100644 index 0000000000..bbb0cfba62 --- /dev/null +++ b/structure/stack/linked_list.go @@ -0,0 +1,83 @@ +package stack + +type node[T any] struct { + Val T + Next *node[T] +} + +// linkedList implements stack.Interface using a singly linked list as the underlying storage +type linkedList[T any] struct { + top *node[T] + length int +} + +func NewLinkedList[T any]() Interface[T] { + return new(linkedList[T]) +} + +// Push value to the top of the stack +func (ll *linkedList[T]) Push(n T) { + newStack := new(node[T]) + + newStack.Val = n + newStack.Next = ll.top + + ll.top = newStack + ll.length++ +} + +// Pop returns last inserted element and removes it from the underlying storage +// If the stack is empty, ErrStackEmpty error is returned +func (ll *linkedList[T]) Pop() (T, error) { + var element T + if ll.Empty() { + return element, ErrStackEmpty + } + element = ll.top.Val + ll.top = ll.top.Next + ll.length-- + return element, nil +} + +// Empty returns true if stack has no elements and false otherwise. +func (ll *linkedList[T]) Empty() bool { + return ll.length == 0 +} + +// Len returns length of the stack +func (ll *linkedList[T]) Len() int { + return ll.length +} + +// Peek return last inserted element(top of the stack) without removing it from the stack +// If the stack is empty, ErrStackEmpty error is returned +func (ll *linkedList[T]) Peek() (T, error) { + var element T + if ll == nil || ll.length == 0 { + return element, ErrStackEmpty + } + return ll.top.Val, nil +} + +// ToSlice returns the elements of stack as a slice +func (ll *linkedList[T]) ToSlice() []T { + var elements []T + if ll == nil { + return elements + } + + current := ll.top + for current != nil { + elements = append(elements, current.Val) + current = current.Next + } + return elements +} + +func (ll *linkedList[T]) Clear() { + if ll == nil { + return + } + ll.top = nil + ll.length = 0 +} diff --git a/structure/stack/linked_list_test.go b/structure/stack/linked_list_test.go new file mode 100644 index 0000000000..58d108b6ee --- /dev/null +++ b/structure/stack/linked_list_test.go @@ -0,0 +1,51 @@ +package stack + +import "testing" + +// TestStackLinkedList for testing stack implementation using singly linked list +func TestStack_SinglyLinkedList(t *testing.T) { + st := NewLinkedList[int]() + + st.Push(1) + st.Push(2) + + t.Run("Stack Push", func(t *testing.T) { + result := st.ToSlice() + expected := []int{2, 1} + for x := range result { + if result[x] != expected[x] { + t.Errorf("Expected stack elements to be %v. Current elements: %v", expected, result) + } + } + }) + + t.Run("Stack isEmpty", func(t *testing.T) { + if st.Empty() { + t.Error("Stack shouldn't be emtpy") + } + }) + + t.Run("Stack Length", func(t *testing.T) { + if st.Len() != 2 { + t.Errorf("Expected stack length to be 2, got %d", st.Len()) + } + }) + + st.Pop() + pop, _ := st.Pop() + + t.Run("Stack Pop", func(t *testing.T) { + if pop != 1 { + t.Errorf("Expected 1 from Pop operation, got %d", pop) + } + }) + + st.Push(52) + st.Push(23) + st.Push(99) + t.Run("Stack Peek", func(t *testing.T) { + if val, _ := st.Peek(); val != 99 { + t.Errorf("Expected 99 from Peek operation, got %d", val) + } + }) +} diff --git a/structure/stack/stack.go b/structure/stack/stack.go new file mode 100644 index 0000000000..872cb2293e --- /dev/null +++ b/structure/stack/stack.go @@ -0,0 +1,33 @@ +package stack + +// Interface defines the specifications for a stack implementation. +// Currently, there are 3 implmentatios of this stack.Interface in this package. +// stack.Array : Uses a slice as its underlying storage +// stack.LinkedList : Uses linked list as its underlying storage +// stack.DoublyLinkedList : Uses the doubly linked list provided by std library `container/list` as the underlying storage. +type Interface[T any] interface { + // Push value to the top of the stack + Push(T) + + // Pop returns last inserted element and removes it from the underlying storage + // If the stack is empty, ErrStackEmpty error is returned + Pop() (T, error) + + // Peek the last inserted element without removing it from the stack + // If the stack is empty, ErrStackEmpty error is returned + Peek() (T, error) + + // Len returns the number of elements in the stack + Len() int + + // Empty returns true if stack has no elements and false otherwise. + Empty() bool + + // Clear removes all elements by re-initializing the underlying storage + // If the underlying storage of the stack is an array/slice (created using `stack.NewArray[T]()`), + // the allocated capacity remains the same and will be reused for subsequent push operations + Clear() + + // ToSlice returns the elements of stack as a slice + ToSlice() []T +} diff --git a/structure/stack/stack_test.go b/structure/stack/stack_test.go deleted file mode 100644 index e493c30b8a..0000000000 --- a/structure/stack/stack_test.go +++ /dev/null @@ -1,124 +0,0 @@ -// Stack Test -// description: based on `geeksforgeeks` description Stack is a linear data structure which follows a particular order in which the operations are performed. -// The order may be LIFO(Last In First Out) or FILO(First In Last Out). -// details: -// Stack Data Structure : https://www.geeksforgeeks.org/stack-data-structure-introduction-program/ -// Stack (abstract data type) : https://en.wikipedia.org/wiki/Stack_(abstract_data_type) -// author [Milad](https://github.com/miraddo) -// see stackarray.go, stacklinkedlist.go, stacklinkedlistwithlist.go - -package stack - -import ( - "container/list" - "testing" -) - -// TestStackLinkedList for testing Stack with LinkedList -func TestStackLinkedList(t *testing.T) { - var newStack Stack - - newStack.push(1) - newStack.push(2) - - t.Run("Stack Push", func(t *testing.T) { - result := newStack.show() - expected := []any{2, 1} - for x := range result { - if result[x] != expected[x] { - t.Errorf("Stack Push is not work, got %v but expected %v", result, expected) - } - } - }) - - t.Run("Stack isEmpty", func(t *testing.T) { - if newStack.isEmpty() { - t.Error("Stack isEmpty is returned true but expected false", newStack.isEmpty()) - } - - }) - - t.Run("Stack Length", func(t *testing.T) { - if newStack.len() != 2 { - t.Error("Stack Length should be 2 but got", newStack.len()) - } - }) - - newStack.pop() - pop := newStack.pop() - - t.Run("Stack Pop", func(t *testing.T) { - if pop != 1 { - t.Error("Stack Pop should return 1 but is returned", pop) - } - - }) - - newStack.push(52) - newStack.push(23) - newStack.push(99) - t.Run("Stack Peak", func(t *testing.T) { - if newStack.peak() != 99 { - t.Error("Stack Peak should return 99 but got ", newStack.peak()) - } - }) -} - -// TestStackLinkedListWithList for testing Stack with Container/List Library (STL) -func TestStackLinkedListWithList(t *testing.T) { - stackList := &SList{ - stack: list.New(), - } - - t.Run("Stack Push", func(t *testing.T) { - - stackList.Push(2) - stackList.Push(3) - - if stackList.Length() != 2 { - t.Errorf("Stack Push is not work we expected %v but got %v", 2, stackList.Length()) - } - }) - - t.Run("Stack Pop", func(t *testing.T) { - pop, _ := stackList.Pop() - - if stackList.Length() == 1 && pop != 3 { - t.Errorf("Stack Pop is not work we expected %v but got %v", 3, pop) - } - }) - - t.Run("Stack Peak", func(t *testing.T) { - - stackList.Push(2) - stackList.Push(83) - peak, _ := stackList.Peak() - if peak != 83 { - t.Errorf("Stack Peak is not work we expected %v but got %v", 83, peak) - } - }) - - t.Run("Stack Length", func(t *testing.T) { - if stackList.Length() != 3 { - t.Errorf("Stack Length is not work we expected %v but got %v", 3, stackList.Length()) - } - }) - - t.Run("Stack Empty", func(t *testing.T) { - if stackList.Empty() == true { - t.Errorf("Stack Empty is not work we expected %v but got %v", false, stackList.Empty()) - } - - d1, err := stackList.Pop() - d2, _ := stackList.Pop() - d3, _ := stackList.Pop() - - if err != nil { - t.Errorf("got an unexpected error %v, pop1: %v, pop2: %v, pop3: %v", err, d1, d2, d3) - } - - if stackList.Empty() == false { - t.Errorf("Stack Empty is not work we expected %v but got %v", true, stackList.Empty()) - } - }) -} diff --git a/structure/stack/stacklinkedlist.go b/structure/stack/stacklinkedlist.go deleted file mode 100644 index 4c49738841..0000000000 --- a/structure/stack/stacklinkedlist.go +++ /dev/null @@ -1,72 +0,0 @@ -// Stack Linked-List -// description: based on `geeksforgeeks` description Stack is a linear data structure which follows a particular order in which the operations are performed. -// The order may be LIFO(Last In First Out) or FILO(First In Last Out). -// details: -// Stack Data Structure : https://www.geeksforgeeks.org/stack-data-structure-introduction-program/ -// Stack (abstract data type) : https://en.wikipedia.org/wiki/Stack_(abstract_data_type) -// author [Milad](https://github.com/miraddo) -// see stacklinkedlistwithlist.go, stackarray.go, stack_test.go - -package stack - -// Node structure -type Node struct { - Val any - Next *Node -} - -// Stack has jost top of node and with length -type Stack struct { - top *Node - length int -} - -// push add value to last index -func (ll *Stack) push(n any) { - newStack := &Node{} // new node - - newStack.Val = n - newStack.Next = ll.top - - ll.top = newStack - ll.length++ -} - -// pop remove last item as first output -func (ll *Stack) pop() any { - result := ll.top.Val - if ll.top.Next == nil { - ll.top = nil - } else { - ll.top.Val, ll.top.Next = ll.top.Next.Val, ll.top.Next.Next - } - - ll.length-- - return result -} - -// isEmpty to check our array is empty or not -func (ll *Stack) isEmpty() bool { - return ll.length == 0 -} - -// len use to return length of our stack -func (ll *Stack) len() int { - return ll.length -} - -// peak return last input value -func (ll *Stack) peak() any { - return ll.top.Val -} - -// show all value as an interface array -func (ll *Stack) show() (in []any) { - current := ll.top - - for current != nil { - in = append(in, current.Val) - current = current.Next - } - return -} diff --git a/structure/stack/stacklinkedlistwithlist.go b/structure/stack/stacklinkedlistwithlist.go deleted file mode 100644 index ba3ce65808..0000000000 --- a/structure/stack/stacklinkedlistwithlist.go +++ /dev/null @@ -1,60 +0,0 @@ -// Stack Linked-List with standard library (Container/List) -// description: based on `geeksforgeeks` description Stack is a linear data structure which follows a particular order in which the operations are performed. -// The order may be LIFO(Last In First Out) or FILO(First In Last Out). -// details: -// Stack Data Structure : https://www.geeksforgeeks.org/stack-data-structure-introduction-program/ -// Stack (abstract data type) : https://en.wikipedia.org/wiki/Stack_(abstract_data_type) -// author [Milad](https://github.com/miraddo) -// see stacklinkedlist.go, stackarray.go, stack_test.go - -package stack - -import ( - "container/list" - "fmt" -) - -// SList is our struct that point to stack with container/list.List library -type SList struct { - stack *list.List -} - -// Push add a value into our stack -func (sl *SList) Push(val any) { - sl.stack.PushFront(val) -} - -// Peak is return last value that insert into our stack -func (sl *SList) Peak() (any, error) { - if !sl.Empty() { - element := sl.stack.Front() - return element.Value, nil - } - return "", fmt.Errorf("stack list is empty") -} - -// Pop is return last value that insert into our stack -// also it will remove it in our stack -func (sl *SList) Pop() (any, error) { - if !sl.Empty() { - // get last element that insert into stack - element := sl.stack.Front() - // remove element in stack - sl.stack.Remove(element) - // return element value - return element.Value, nil - } - return "", fmt.Errorf("stack list is empty") -} - -// Length return length of our stack -func (sl *SList) Length() int { - return sl.stack.Len() -} - -// Empty check our stack has value or not -func (sl *SList) Empty() bool { - // check our stack is empty or not - // if is 0 it means our stack is empty otherwise is not empty - return sl.stack.Len() == 0 -}