Goでのスタックの実装
概要
Goの標準パッケージにはスタックは含まれていない様です。
そこでスライスを使ってint
を格納するスタックを実装してみました。
環境
golang v1.12.5
実装したメソッド
- Push: 値をトップに追加
- Pop: 値をトップから削除して返す
- Peek: トップの値を返す
- Size: サイズを返す
- Empty: 空の場合にtrueを返す
コード
package main import ( "fmt" ) // Stackは[]intのエイリアス type Stack []int // Push adds an element func (s *Stack) Push(v int) { *s = append(*s, v) } // Pop removes the top element and return it func (s *Stack) Pop() (int, error) { if s.Empty() { return 0, fmt.Errorf("stack is empty") } v := (*s)[len(*s)-1] *s = (*s)[:len(*s)-1] return v, nil } // Peek returns the top value func (s *Stack) Peek() (int, error) { if s.Empty() { return 0, fmt.Errorf("stack is empty") } return (*s)[len(*s)-1], nil } // Size returns the length of stack func (s *Stack) Size() int { return len(*s) } // Empty returns true when stack is empty func (s *Stack) Empty() bool { return s.Size() == 0 } // NewStack generates stack func NewStack() *Stack { s := new(Stack) return s } func main() { s := NewStack() s.Push(0) s.Push(1) fmt.Println(*s) // [0 1] fmt.Println(s.Size()) // 2 fmt.Println(s.Empty()) // false if v, err := s.Pop(); err == nil { fmt.Println(v) // 1 } else { fmt.Println(err) os.Exit(1) } fmt.Println(*s) // [0] if v, err := s.Peek(); err == nil { fmt.Println(v) // 0 } else { fmt.Println(err) os.Exit(1) } s.Pop() fmt.Println(s.Empty()) // true }
解説
PushやPop等のスタックを変更するメソッドは、ポインタレシーバとしてStackを受け取る必要があります。
この理由は、(Stackは[]int
のエイリアスなので)スライスを関数に渡すときの挙動に由来します。
スライスを関数に渡したときの挙動についてはこの記事が分かりやすかったです。
簡単にまとめると、
- スライスは配列へのポインタを持つ
- スライス構造体自体は値渡しされるとコピーされるが、参照先は不変
- スライスのappend時にcapacityが足りない場合は参照先配列を再配置する
つまり、スライスを値渡ししてappendしても、capacityが足りなかった場合は異なる配列に値が追加されてしまい、元のスライスが指す配列は変更されないということです。
Stackを生成する際に多めにcapacityを取っておけば問題はないのですが、それもちょっと気持ち悪いので、スライスをポインタ渡しすることで再配置されても大丈夫な実装にしました。
ポインタ渡しされているので、[]int
の操作をしたい場合はデリファレンス(*s
)する必要があります。
PopやPeekはスタックが空の場合はerrorを返すようにしています。