shomanのブログ

ただの備忘録

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エイリアスなので)スライスを関数に渡すときの挙動に由来します。

スライスを関数に渡したときの挙動についてはこの記事が分かりやすかったです。

christina04.hatenablog.com

簡単にまとめると、

  • スライスは配列へのポインタを持つ
  • スライス構造体自体は値渡しされるとコピーされるが、参照先は不変
  • スライスのappend時にcapacityが足りない場合は参照先配列を再配置する

つまり、スライスを値渡ししてappendしても、capacityが足りなかった場合は異なる配列に値が追加されてしまい、元のスライスが指す配列は変更されないということです。

Stackを生成する際に多めにcapacityを取っておけば問題はないのですが、それもちょっと気持ち悪いので、スライスをポインタ渡しすることで再配置されても大丈夫な実装にしました。

ポインタ渡しされているので、[]intの操作をしたい場合はデリファレンス(*s)する必要があります。

PopやPeekはスタックが空の場合はerrorを返すようにしています。