Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancements for stack implementation using slice #544

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions structure/stack/array.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package stack

import "errors"

var ErrStackEmpty = errors.New("stack is empty")

// 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]() Interface[T] {
return new(Array[T])
}

// Push inserts a new element to the stack
// Push on a nil stack will panic
func (s *Array[T]) Push(val T) {
s.store = append(s.store, val)
}

// Peek the last inserted element without removing it from the stack
// If the stack is empty, ErrStackEmpty error is returned
func (s *Array[T]) Peek() (T, error) {
var element T
if s.Empty() {
return element, ErrStackEmpty
}
return s.store[s.Len()-1], nil
}

func (s *Array[T]) Len() int {
if s == nil {
return 0
}
return len(s.store)
}

func (s *Array[T]) Empty() bool {
return s.Len() == 0
}

// 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
if s.Empty() {
return element, ErrStackEmpty
}
element = s.store[s.Len()-1]
s.store = s.store[:s.Len()-1]
return element, nil
}

// Clear removes all elements.
// The allocated capacity remains the same and will be reused for subsequent push operations
func (s *Array[T]) Clear() {
if s == nil {
return
}
s.store = s.store[:0]
}

func (s *Array[T]) ToSlice() []T {
out := make([]T, len(s.store))
copy(out, s.store)
return out
}
45 changes: 45 additions & 0 deletions structure/stack/array_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package stack

import (
"errors"
"testing"
)

func Test_StackArray(t *testing.T) {
stack := NewArray[int]()
_, err := stack.Peek()
if !errors.Is(err, ErrStackEmpty) {
t.Errorf("Expected error ErrStackEmpty from Peek operation, got %v", err)
}

_, err = stack.Pop()
if !errors.Is(err, ErrStackEmpty) {
t.Errorf("Expected error ErrStackEmpty from Pop operation, got %v", err)
}

stack.Push(2)
stack.Push(3)
pop, err := stack.Pop()
if err != nil {
t.Errorf("Expected no errors in Pop operation, got %v", err)
}
if stack.Len() != 1 {
t.Errorf("Expected stack length 1, got %d", stack.Len())
}
if pop != 3 {
t.Errorf("Expected popped element to be 3, got %d", pop)
}

peek, err := stack.Peek()
if err != nil {
t.Errorf("Expected no errors in Peek operation, got %v", err)
}
if peek != 2 {
t.Errorf("Expected peek operation to return element 3, got %d", peek)
}

stack.Clear()
if stack.Len() != 0 && !stack.Empty() {
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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use our internal data structures when creating new ones. Using container package hides a lot of material that could be used for learning.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was already present (I just renamed the file). Since there is another implementation using linked list, shall I remove this one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep it there, but create a new Issue to fix this 🙏🏼

)

// 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
}
57 changes: 57 additions & 0 deletions structure/stack/doubly_linked_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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
}
Loading