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

RM-23161 Implemented LL based Stack & tests #177

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ structure which preserve and reuse previous versions. This uses a very
functional, cons-style of list manipulation. Insert, get, remove, and size
operations are O(n) as you would expect.

#### Stack

A persistent, immutable stack providing time efficient operations. All operations are applied to the stack itself and don't need
Copy link
Collaborator

Choose a reason for hiding this comment

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

This isn't persistent/immutable is it? I'm not saying it should be, but you modify the internal state in all the operations

the user to reassign the stack every time. Push and Pop operations are *O(1)*. Since Go is gc'ed
the function Clear is also *O(1)*. DropWhile and PopWhile are *O(k < n)* for *k* predicate matches.

### Installation

1. Install Go 1.3 or higher.
Expand Down
1 change: 1 addition & 0 deletions datastructures.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
_ "github.com/Workiva/go-datastructures/slice"
_ "github.com/Workiva/go-datastructures/slice/skip"
_ "github.com/Workiva/go-datastructures/sort"
_ "github.com/Workiva/go-datastructures/stack"
_ "github.com/Workiva/go-datastructures/threadsafe/err"
_ "github.com/Workiva/go-datastructures/tree/avl"
_ "github.com/Workiva/go-datastructures/trie/xfast"
Expand Down
159 changes: 159 additions & 0 deletions stack/stack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
Copyright 2015 Workiva, LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/* Package stack implements a simple linked-list based stack */
package stack

import "errors"

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

// Stack is an immutable stack
type Stack interface {
// Top returns the top-most item of the stack.
// If the stack is empty, the bool is set to false.
Top() (interface{}, bool)

// Pop returns the top-most item of the stack and removes it.
// The error is set to ErrEmptyStack should the stack be empty.
Pop() (interface{}, error)

// Drop drops the top-most item of the stack.
// Error is set to ErrEmptyStack should the stack be empty.
Drop() error

// Push pushes an item onto the stack.
Push(interface{})

// PopWhile creates a channel of interfaces and pops items from the stack
// as long as the predicate passed holds or the stack is emptied.
PopWhile(func(interface{}) bool) []interface{}

// DropWhile drops items from the stack as long as the predicate passed holds or the stack is emptied.
DropWhile(func(interface{}) bool)

// IsEmpty returns whether the stack is empty.
IsEmpty() bool

// Size returns the amount of items in the stack.
Size() uint

// Clear empties the stack
Clear()
}

type stack struct {
size uint
top *item
}

type item struct {
item interface{}
next *item
}

// Top returns the top-most item of the stack.
// If the stack is empty, the bool is set to false.
func (s *stack) Top() (interface{}, bool) {
if s.top == nil {
return nil, false
}
return s.top.item, true
}

// Pop returns the top-most item of the stack and removes it.
// The error is set to ErrEmptyStack should the stack be empty.
func (s *stack) Pop() (interface{}, error) {
if s.IsEmpty() {
return nil, ErrEmptyStack
}

s.size--
top := s.top
s.top = s.top.next
return top.item, nil
}

// Drop drops the top-most item of the stack.
// Error is set to ErrEmptyStack should the stack be empty.
func (s *stack) Drop() error {
if s.IsEmpty() {
return ErrEmptyStack
}

s.size--
s.top = s.top.next
return nil
}

// Push pushes an item onto the stack.
func (s *stack) Push(it interface{}) {
s.size++
s.top = &item{it, s.top}
}

// PopWhile creates a channel of interfaces and pops items from the stack
// as long as the predicate passed holds or the stack is emptied.
func (s *stack) PopWhile(pred func(interface{}) bool) []interface{} {
its := make([]interface{}, 0)
for !s.IsEmpty() {
// We are sure this cannot return an error
it, _ := s.Top()
if pred(it) {
s.Pop()
its = append(its, it)
continue
}
break
}
return its
}

// DropWhile drops items from the stack as long as the predicate passed holds or the stack is emptied.
func (s *stack) DropWhile(pred func(interface{}) bool) {
for !s.IsEmpty() {
// We are sure this cannot return an error
it, _ := s.Top()
if pred(it) {
s.Pop()
continue
}
return
}
}

// IsEmpty returns whether the stack is empty.
func (s *stack) IsEmpty() bool {
return s.size == 0
}

// Size returns the amount of items in the stack.
func (s *stack) Size() uint {
return s.size
}

// Clear empties the stack
func (s *stack) Clear() {
s.size = 0
s.top = nil
}

// Empty returns a new empty stack
func Empty() Stack {
return &stack{0, nil}
}
89 changes: 89 additions & 0 deletions stack/stack_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package stack

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestEmptyStack(t *testing.T) {
assert := assert.New(t)
s := Empty()
top, ok := s.Top()
assert.Nil(top)
assert.False(ok)

top, err := s.Pop()
assert.Nil(top)
assert.Equal(err, ErrEmptyStack)

assert.True(s.IsEmpty())
}

func TestPushPop(t *testing.T) {
assert := assert.New(t)
s := Empty()

// stack [10]
s.Push(10)
assert.False(s.IsEmpty())
top, ok := s.Top()
assert.True(ok)
assert.Equal(top, 10)
assert.Equal(uint(1), s.Size())

s.Push(3)
// stack [3 10]
assert.Equal(uint(2), s.Size())
top, err := s.Pop()
assert.Nil(err)
assert.Equal(top, 3)
assert.Equal(uint(1), s.Size())
}

func TestPopDropWhile(t *testing.T) {
assert := assert.New(t)
s := Empty()
for i := 0; i < 11; i++ {
s.Push(i * i)
}
assert.Equal(uint(11), s.Size())

pred := func(it interface{}) bool {
return it.(int) >= 64
}

its := s.PopWhile(pred)

for _, it := range its {
assert.True(pred(it))
}

assert.Equal(uint(8), s.Size())

pred = func(it interface{}) bool {
return s.Size() > 3
}

s.DropWhile(pred)

assert.Equal(uint(3), s.Size())
}

func TestClearStack(t *testing.T) {
assert := assert.New(t)

s := Empty()
s.Push("a")
s.Push("b")
s.Push("c")
s.Push("d")

assert.Equal(uint(4), s.Size())
top, ok := s.Top()
assert.True(ok)
assert.Equal(top, "d")

s.Clear()
assert.True(s.IsEmpty())
}