Skip to content

Commit

Permalink
Expose an interface for the stack package
Browse files Browse the repository at this point in the history
  • Loading branch information
BulkBeing committed Oct 21, 2022
1 parent d2dbcf7 commit 67b4562
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 280 deletions.
30 changes: 13 additions & 17 deletions structure/stack/stack_array.go → structure/stack/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}

Expand Down Expand Up @@ -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
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
93 changes: 93 additions & 0 deletions structure/stack/doubly_linked_list.go
Original file line number Diff line number Diff line change
@@ -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
}
66 changes: 66 additions & 0 deletions structure/stack/doubly_linked_list_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
})
}
83 changes: 83 additions & 0 deletions structure/stack/linked_list.go
Original file line number Diff line number Diff line change
@@ -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
}
51 changes: 51 additions & 0 deletions structure/stack/linked_list_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
Loading

0 comments on commit 67b4562

Please sign in to comment.