From 4284aad0aafecd6ddd1f51c27ffe265a3a225c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Dano?= Date: Thu, 28 Sep 2017 10:29:20 +0200 Subject: [PATCH 1/2] Implemented LL based Stack & tests --- README.md | 6 ++ datastructures.go | 1 + stack/stack.go | 161 ++++++++++++++++++++++++++++++++++++++++++++ stack/stack_test.go | 89 ++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 stack/stack.go create mode 100644 stack/stack_test.go diff --git a/README.md b/README.md index 75353bb..68b2371 100644 --- a/README.md +++ b/README.md @@ -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 +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. diff --git a/datastructures.go b/datastructures.go index d09926d..49677ac 100644 --- a/datastructures.go +++ b/datastructures.go @@ -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" diff --git a/stack/stack.go b/stack/stack.go new file mode 100644 index 0000000..d367c11 --- /dev/null +++ b/stack/stack.go @@ -0,0 +1,161 @@ +/* +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-- + top := s.top + ntop := top.next + s.top = ntop + 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} +} diff --git a/stack/stack_test.go b/stack/stack_test.go new file mode 100644 index 0000000..4220c1a --- /dev/null +++ b/stack/stack_test.go @@ -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()) +} From cfa5aa85948c866a89988df0ba5d8db9c4316f58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Dano?= Date: Thu, 28 Sep 2017 10:46:21 +0200 Subject: [PATCH 2/2] Removed useless statements in stack.Drop --- stack/stack.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/stack/stack.go b/stack/stack.go index d367c11..ac0a68f 100644 --- a/stack/stack.go +++ b/stack/stack.go @@ -97,9 +97,7 @@ func (s *stack) Drop() error { } s.size-- - top := s.top - ntop := top.next - s.top = ntop + s.top = s.top.next return nil }