A stack is an ordered collection of items following the Last-In-First-Out (LIFO) principle. We add and remove items from the same part of the pile, called the top. We cannot remove items from the base. Just like a pile of books.

Stacks have tons of uses, from creating a history of pages visited to commands entered and to store actions that can be undone.

Implementation

Internally the stack will be represented with a slice type, and I’ll expose the

  • Push()
  • Pull()
  • New()

methods.

New() serves as the constructor, which initializes the internal slice when we start using it.

I’ll create an ItemStack generic type, concurrency safe, that can generate stacks containing any type by using genny, to create a type-specific stack implementation, encapsulating the actual value-specific data structure containing the data.

// Package stack creates a ItemStack data structure for the Item type
package stack

import (
    "sync"

    "github.com/cheekybits/genny/generic"
)

// Item the type of the stack
type Item generic.Type

// ItemStack the stack of Items
type ItemStack struct {
    items []Item
    lock  sync.RWMutex
}

// New creates a new ItemStack
func (s *ItemStack) New() *ItemStack {
    s.items = []Item{}
    return s
}

// Push adds an Item to the top of the stack
func (s *ItemStack) Push(t Item) {
    s.lock.Lock()
    s.items = append(s.items, t)
    s.lock.Unlock()
}

// Pop removes an Item from the top of the stack
func (s *ItemStack) Pop() *Item {
    s.lock.Lock()
    item := s.items[len(s.items)-1]
    s.items = s.items[0 : len(s.items)-1]
    s.lock.Unlock()
    return &item
}

Tests

The tests describe the usage of the above implementation.

package stack

import (
    "testing"
)

var s ItemStack

func initStack() *ItemStack {
    if s.items == nil {
        s = ItemStack{}
        s.New()
    }
    return &s
}

func TestPush(t *testing.T) {
    s := initStack()
    s.Push(1)
    s.Push(2)
    s.Push(3)
    if size := len(s.items); size != 3 {
        t.Errorf("wrong count, expected 3 and got %d", size)
    }
}

func TestPop(t *testing.T) {
    s.Pop()
    if size := len(s.items); size != 2 {
        t.Errorf("wrong count, expected 2 and got %d", size)
    }

    s.Pop()
    s.Pop()
    if size := len(s.items); size != 0 {
        t.Errorf("wrong count, expected 0 and got %d", size)
    }
}

Creating a concrete stack data structure

You can use this generic implemenation to generate type-specific stacks, using

//generate a `IntStack` stack of `int` values
genny -in stack.go -out stack-int.go gen "Item=int"

//generate a `StringStack` stack of `string` values
genny -in stack.go -out stack-string.go gen "Item=string"