middleman deplyが動いてくれない

www.panicblanket.com にホームページを作った。とはいっても2ページしか無いんだけど。
作成には Ruby 製静的サイトジェネレータの Middleman を使った。Middleman の使い方はググればいっぱい出てくるのでここでは省略。とりあえず日本語の公式サイトへのリンクだけ載せておこう。

 cf. Middleman

基本的にはこの公式サイトの説明のとおりにやっていけば、サイトを作れる。というか作れた。
問題はサーバへのデプロイだ。デプロイは、要するにサーバの然るべきディレクトリにファイルをコピーすればいいので、FTP でも何でもいいんだけど、ファイル数が多くなればそれだけ面倒になる。で、公式サイトでは middleman-deploy というプラグインを使うといい、みたいなことが書いてあったのでそうすることにした。
ところがこれが罠だったわけだ。使い方は簡単、Gemfile に middleman-deploy を追加して、bundle install --path vendor/bundle コマンドでインストール。config.rb にデプロイのための設定を書き足して(今回は FTP を使うことにした)、bundle exec middleman deploy を実行するだけ。
で、実行したところこんなエラーが出た。

takatoh@envelopes $ bundle exec middleman deploy
/home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/extensions.rb:96:in `load': Tried to activate old-style extension: deploy. They are no longer supported. (RuntimeError)
	from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/extensions.rb:127:in `block in load_settings'
	from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/extensions.rb:125:in `each'
	from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/extensions.rb:125:in `load_settings'
	from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/extension_manager.rb:12:in `initialize'
	from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/application.rb:263:in `new'
	from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/application.rb:263:in `initialize'
	from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-cli-4.2.1/bin/middleman:49:in `new'
	from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-cli-4.2.1/bin/middleman:49:in `'
	from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/bin/middleman:23:in `load'
	from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/bin/middleman:23:in `
'

長いけど、最初のところに、「Tried to activate old-style extension: deploy. They are no longer supported. (RuntimeError)」って書いてある。deploy は古いスタイルの拡張で、すでにサポートされていない、らしい。GitHub の middleman-deploy のページを見てみると、インストールしたバージョン 1.0 がリリースされたのが 2014 年。これ以降新しいバージョンのリリースがない。そりゃ古いよなぁ。でも公式サイトに書いてあるんだぜ?どうしろってんだ。

結局、FTP でファイルをひとつずつアップした。スタイルシートを含めても3ファイルしか無いので、大したことはなかったけど、これからファイル数が増えたらなんか考えなきゃいけない。みんなどうしてるんだろう?

JSONを整形するツール

本当はゴルーチンについて書きたいんだけど、まだ頭の中で整理ができてないので今日は別のことを書く。

Web サービスやなんかの API で JSON を返してくれるのはよくあること。ただ、基本的にプログラムが処理するように想定されていて、人間が見やすい形にはなっていないことが多い。まあ、当たり前ではあるんだけど、それでも目で見て確かめたいときもある。そういう時は見やすく整形してくれるツールがほしくなる。
ググると jq っていうツールが見つかる。このツール自体は、JSON の整形だけでなく検索とかいろいろできるようで、おまけに Windows 用のバイナリもあるのでちょっと使ってみた。
ところが!もとの JSON が UTF-8 のせいかもしれないけど、Windows のコマンドプロンプトでは日本語が文字化けしてしまって読めない。おまけにどういうわけか、コマンドプロンプトのフォントが変更されてしまうという、謎の現象に見舞われた。
これでは使えないので、じゃあ、Go で整形するだけのツールを書いてみようか、と思って書いたら意外にも簡単だった、というのが今日の話。

サンプルの JSON はこんなの。Web で拾ったサンプルだけど、どこのページだか忘れてしまった。

{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "pref": "富山県", "city1": "下新川郡", "city2": "朝日町" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 136.111111, 36.111111 ], [ 136.222222, 36.222222 ] ] ] } }, { "type": "Feature", "properties": { "pref": "富山県", "city1": "氷見市", "city2": "" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 136.333333, 36.333333 ], [ 136.444444, 36.444444 ] ] ] } }, { "type": "Feature", "properties": { "pref": "富山県", "city1": "高岡市", "city2": "" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 136.555555, 36.555555 ], [ 138.666666, 36.666666 ] ] ] } } ]}

全部が1行に詰め込まれてる上に、日本語が混じっている。

で、書いたツールがこれ。pj という名前にした(ファイル名は main.go だけど)。整形には encoding/json パッケージの Indent 関数を使っている。

package main

import (
    "fmt"
    "encoding/json"
    "bytes"
    "os"
    "io/ioutil"
    "flag"
)

const (
    progVersion = "v0.1.0"
)

func prettyJson(src []byte) string {
    buf := make([]byte, 0)
    dst := bytes.NewBuffer(buf)
    err := json.Indent(dst, src, "", " ")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    return dst.String()
}

func main() {
    flag.Usage = func() {
    fmt.Fprintf(os.Stderr,
    `Usage:
%s [options] 
Options:
`, os.Args[0])
    flag.PrintDefaults()
}
    opt_version := flag.Bool("version", false, "Show version.")
    flag.Parse()

    if *opt_version {
        fmt.Println(progVersion)
        os.Exit(0)
    }

    infile := flag.Args()[0]
    src, err := ioutil.ReadFile(infile)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    pretty := prettyJson(src)
    fmt.Println(pretty)
}

試してみよう。

^o^ > go build

^o^ > pj sample.json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "pref": "富山県",
        "city1": "下新川郡",
        "city2": "朝日町"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              136.111111,
              36.111111
            ],
            [
              136.222222,
              36.222222
            ]
          ]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "pref": "富山県",
        "city1": "氷見市",
        "city2": ""
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              136.333333,
              36.333333
            ],
            [
              136.444444,
              36.444444
            ]
          ]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "pref": "富山県",
        "city1": "高岡市",
        "city2": ""
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              136.555555,
              36.555555
            ],
            [
              138.666666,
              36.666666
            ]
          ]
        ]
      }
    }
  ]
}

何のデータなんだかよくわからないけど、うまくいった。もとのエンコーディングは UTF-8 だけど、Windows のコマンドプロンプトに出力しても文字化けしない。もちろんファイルに書き出せばちゃんと UTF-8 で保存される。
うまくいったので GitHub にも公開しておいた。

cf. https://github.com/takatoh/pj

インストールするにはこうすればいい:

^o^ > go get github.com/takatoh/pj

定型データの入力

定型のデータを読み込んで、型変換をしてくれる関数があると便利だ。
fmt パッケージの ScanFscan は空白文字で区切られたテキストデータを読み込み、可変長引数で渡されたデータ形式の変数(のポインタ)に変換して格納してくれる。Scan は標準入力から、Fscanio.Reader から読み込む。
まずは Scan の例。

package main

import (
    "fmt"
    "os"
    "io"
)

func main() {
    sumi := 0
    sumf := 0.0
    suma := make([]string, 0)

    for {
        var n int
        var m float64
        var s string
        i, err := fmt.Scan(&n, &m, &s)
        if i == 3 {
            sumi += n
            sumf += m
            suma = append(suma, s)
        } else if i == 0 && err == io.EOF {
            break
        } else {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
    }
    fmt.Println(sumi, sumf, suma)
}
^o^ > go run scan.go
1 1.5 1
2 2.3 2
3 3.8 3
6 7.6 [1 2 3]

go run scan.go コマンドに続く3行が入力で、1行ごとに整数、実数、文字列に変換される。ctrl + c で入力を終了すると、結果が表示される。最後の行が結果だ。

次は Fscan の例。io.Reader から(つまりファイルから)読み込む。

package main

import (
    "fmt"
    "os"
    "io"
)

func main() {
    sumi := 0
    sumf := 0.0
    suma := make([]string, 0)

    filename := os.Args[1]
    file, err := os.Open(filename)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    for {
        var n int
        var m float64
        var s string
        i, err := fmt.Fscan(file, &n, &m, &s)
        if i == 3 {
            sumi += n
            sumf += m
            suma = append(suma, s)
        } else if i == 0 && err == io.EOF {
            break
        } else {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
    }
    file.Close()
    fmt.Println(sumi, sumf, suma)
}
^o^ > cat fscan_sample.txt
10 1.234 foo
20 5.678 bar
30 9.876 baz

^o^ > go run fscan.go fscan_sample.txt
60 16.788 [foo bar baz]

ScanfFscanf は書式付き入力だ。引数で指定された書式で入力を解釈して読み込んでくれる。Scanf は標準入力から、Fscanfio.Reader (つまりファイル)から読み込む。
Scanf の例。

package main

import (
    "fmt"
    "os"
)

func main() {
    sumi := 0
    sumf := 0.0
    suma := make([]string, 0)
    for {
        var n int
        var m float64
        var s string
        i, err := fmt.Scanf("%d,%f,%q\n", &n, &m, &s)
        if i == 3 {
            sumi += n
            sumf += m
            suma = append(suma, s)
        } else if i == 0 {
            break
        } else {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
    }
    fmt.Println(sumi, sumf, suma)
}

この例では整数、実数、クォートされた文字列をカンマ区切りしたものを入力として期待している。

^o^ > go run scanf.go
10,1.234,"foo"
20,5.678,"bar"
30,9.876,"baz"
60 16.788 [foo bar baz]

最後に Fscanf。これはファイルから読み込むことのほかは Scanf と同じだ。

package main

import (
    "fmt"
    "os"
)

func main() {
    sumi := 0
    sumf := 0.0
    suma := make([]string, 0)

    filename := os.Args[1]
    file, err := os.Open(filename)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }

    for {
        var n int
        var m float64
        var s string
        i, err := fmt.Fscanf(file, "%d,%f,%q\n", &n, &m, &s)
        if i == 3 {
            sumi += n
            sumf += m
            suma = append(suma, s)
        } else if i == 0 {
            break
        } else {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
    }
    file.Close()
    fmt.Println(sumi, sumf, suma)
}
^o^ > cat fscanf_sample.txt
10,1.234,"foo"
20,5.678,"bar"
30,9.876,"baz"

^o^ > go run fscanf.go fscanf_sample.txt
60 16.788 [foo bar baz]

cat

ファイル入出力の練習に cat コマンドを写経してみた。
まずひとつめ、bufioReadString 関数で1行ずつ処理。

package main

import (
    "os"
    "fmt"
    "io"
    "bufio"
)

func cat(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    rd := bufio.NewReader(file)
    for {
        s, err := rd.ReadString('\n')
        if err == io.EOF { break }
        fmt.Print(s)
    }
    file.Close()
}

func main() {
    for _, name := range os.Args[1:] {
        cat(name)
    }
}
^o^ > go build cat.go

^o^ > .\cat cat.go cat2.go
package main

import (
        "os"
        "fmt"
        "io"
        "bufio"
)

func cat(filename string) {
        file, err := os.Open(filename)
        if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
        }
        rd := bufio.NewReader(file)
        for {
                s, err := rd.ReadString('\n')
                if err == io.EOF { break }
                fmt.Print(s)
        }
        file.Close()
}

func main() {
        for _, name := range os.Args[1:] {
                cat(name)
        }
}
package main

import (
        "os"
        "fmt"
        "io/ioutil"
)

func cat(filename string) {
        buff, err := ioutil.ReadFile(filename)
        if err != nil {
                fmt.Fprintln(os.Stderr, err)
                os.Exit(1)
        }
        os.Stdout.Write(buff)
}

func main() {
        for _, name := range os.Args[1:] {
                cat(name)
        }
}

もうひとつ、io/ioutil パッケージの ReadFile 関数でファイルまるごと読み込む。

package main

import (
    "os"
    "fmt"
    "io/ioutil"
)

func cat(filename string) {
    buff, err := ioutil.ReadFile(filename)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    os.Stdout.Write(buff)
}

func main() {
    for _, name := range os.Args[1:] {
        cat(name)
    }
}

実行結果はひとつめと同じなので省略。

ファイル入出力

今日はファイル入出力。
前に書いたように、ファイル入出力にはファイルディスクリプタを使う。ファイルディスクリプタは、os パッケージの Open 関数で取得する。取得したファイルディスクリプタで、ファイルからの入力には Read 関数、出力には Write 関数を使い、終わったら Close する。まあ、普通の手順だよな。
以下、サンプル。testin.txt ファイルから読み込んだ内容を testout.txt ファイルに書き込んでいる。

package main

import (
    "os"
    "fmt"
)

func main() {
    input, err := os.Open("testin.txt")
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    output, _ := os.Create("testout.txt")
    buff := make([]byte, 256)
    for {
        c, _ := input.Read(buff)
        if c == 0 { break }
        output.Write(buff[:c])
    }
    input.Close()
    output.Close()
}
^o^ > cat testin.txt
Hello, Golang!

^o^ > go run fileio.go

^o^ > cat testout.txt
Hello, Golang!

bufio

入出力をバイト単位や行単位で行いたいときには bufio パッケージの関数が便利。リーダーやライターを作ってから、入出力関数を呼び出す。
バイト単位には、ReadByteWriteByte がいい。

package main

import (
    "os"
    "io"
    "bufio"
)

func main() {
    r := bufio.NewReader(os.Stdin)
    w := bufio.NewWriter(os.Stdout)
    for {
        c, err := r.ReadByte()
        if err == io.EOF { break }
        w.WriteByte(c)
        if c == '\n' { w.Flush() }
    }
    w.Flush()
}

10行目と11行目でリーダーとライターを作って、それを使っている。

^o^ > go build echo1.go

^o^ > echo1.exe < echo1.go
package main

import (
        "os"
        "io"
        "bufio"
)

func main() {
        r := bufio.NewReader(os.Stdin)
        w := bufio.NewWriter(os.Stdout)
        for {
                c, err := r.ReadByte()
                if err == io.EOF { break }
                w.WriteByte(c)
                if c == '\n' { w.Flush() }
        }
        w.Flush()
}

もうひとつ、行単位で処理するには、同じリーダー、ライターの ReadStringWriteString が使える。

package main

import (
    "os"
    "io"
    "bufio"
)

func main() {
    r := bufio.NewReader(os.Stdin)
    w := bufio.NewWriter(os.Stdout)
    for {
        s, err := r.ReadString('\n')
        if err == io.EOF { break }
        w.WriteString(s)
        w.Flush()
    }
}
^o^ > go build echo2.go

^o^ > echo2.exe < echo2.go
package main

import (
        "os"
        "io"
        "bufio"
)

func main() {
        r := bufio.NewReader(os.Stdin)
        w := bufio.NewWriter(os.Stdout)
        for {
                s, err := r.ReadString('\n')
                if err == io.EOF { break }
                w.WriteString(s)
                w.Flush()
        }
}

pyenvとvirtualenvを使ってみる

Python は 2.7.12 を常用しているけど、Python3 系に対応する必要があったので、これを機会にやってみることにした。まあ、今時 Python2 系だけってものなんだと思うので、ちょうどいいといえばそのとおりではある。
で、複数のバージョンを共存させるには pyenv を使うのがいいらしい。virtualenv については、以前エントリを書いたこともあるけど、pyenv と合わせて使うには pyenv のプラグインである pyenv-virtualenv を使うといいらしいので、そうすることにする。

環境

  • Ubuntu 16.04 LTS
  • システムの Python : 2.7.12

インストール

pyenv のインストール。インストールというか github からクローンする。システムにインストールする方法と、ユーザーディレクトリにインストールする方法があるようだけど、今回は後者にした。

takatoh@envelopes $ git clone https://github.com/yyuu/pyenv.git ~/.pyenv

pyenv-virtualenv のインストール

takatoh@envelopes $ git clone https://github.com/yyuu/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv

.bachrcに設定を書き加える

以下を追記

export PYENV_ROOT=$HOME/.pyenv
export PATH=$PYENV_ROOT/bin:$PATH
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

書いたらいったん端末を起動し直す。

Pythonのインストール

特定のバージョンをインストールするには pyenv install コマンド。-l オプションをつけると、インストール可能なバージョンの一覧が見られる。すげーいっぱい出てくるけど、今回は 3.6.3 をインストールしてみた。

takatoh@envelopes $ pyenv install 3.6.3
Downloading Python-3.6.3.tar.xz...
-> https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tar.xz
Installing Python-3.6.3...
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
WARNING: The Python readline extension was not compiled. Missing the GNU readline lib?
Installed Python-3.6.3 to /home/takatoh/.pyenv/versions/3.6.3

おっと、なんか WARNING がでたな。コンパイルするときにライブラリが足りないようだ。

takatoh@envelopes $ sudo apt install libbz2-dev libreadline6-dev

インストールした 3.6.3 をいったんアンインストールしてやり直し。

takatoh@envelopes $ pyenv uninstall 3.6.3
pyenv: remove /home/takatoh/.pyenv/versions/3.6.3? Y
takatoh@envelopes $ pyenv install 3.6.3
Downloading Python-3.6.3.tar.xz...
-> https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tar.xz
Installing Python-3.6.3...
Installed Python-3.6.3 to /home/takatoh/.pyenv/versions/3.6.3

今度はうまくいった。
インストールしたバージョンは pyenv versions コマンドで見られる。

takatoh@envelopes $ pyenv versions
* system (set by /home/takatoh/.pyenv/version)
  3.6.3

* がついてるのがアクティブなバージョンだ。

バージョンの切り替え

上に書いたように、現状ではシステムにインストールされている Python が使われている。これを切り替えるには、pyenv global または pyenv local コマンドを使う。global は全体に、local はカレントディレクトリに適用される。

takatoh@envelopes $ pyenv local 3.6.3
takatoh@envelopes $ pyenv versions
  system
* 3.6.3 (set by /home/takatoh/.python-version)
takatoh@envelopes $ python -V
Python 3.6.3

つぎの作業に入る前にいったんシステムのバージョンに戻しておく。

virtualenvで新しい環境を作る

pyenv virtualenv [version] <virtualenv-name> とすることで新しい環境を作れる。

takatoh@envelopes $ pyenv virtualenv 3.6.3 brs-3.6.3
Requirement already satisfied: setuptools in /home/takatoh/.pyenv/versions/3.6.3/envs/brs-3.6.3/lib/python3.6/site-packages
Requirement already satisfied: pip in /home/takatoh/.pyenv/versions/3.6.3/envs/brs-3.6.3/lib/python3.6/site-packages
takatoh@envelopes $ pyenv local brs-3.6.3
(brs-3.6.3) takatoh@envelopes $

プロンプトのカッコ内に環境名が表示されている。この環境は保存されていて、以後、このディレクトリに戻ってくるたびに自動的に適用される。

(brs-3.6.3) takatoh@envelopes $ cd ..
takatoh@envelopes $ pyenv versions
* system (set by /home/takatoh/.python-version)
  3.6.3
  3.6.3/envs/brs-3.6.3
  brs-3.6.3
takatoh@envelopes $ cd brs
(brs-3.6.3) takatoh@envelopes $ pyenv versions
  system
  3.6.3
  3.6.3/envs/brs-3.6.3
* brs-3.6.3 (set by /home/takatoh/w/brs/.python-version)
(brs-3.6.3) takatoh@envelopes $

パッケージのインストール

通常通り pip コマンドを使う。

(brs-3.6.3) takatoh@envelopes $ pip list --format=columns
Package    Version
---------- -------
pip        9.0.1  
setuptools 28.8.0 
(brs-3.6.3) takatoh@envelopes $ pip install click
Collecting click
  Using cached click-6.7-py2.py3-none-any.whl
Installing collected packages: click
Successfully installed click-6.7
(brs-3.6.3) takatoh@envelopes $ pip list --format=columns
Package    Version
---------- -------
click      6.7    
pip        9.0.1  
setuptools 28.8.0

とりあえずこんなもんかな。

標準入出力

ファイル入出力はファイルディスクリプタを介して行う。要するにファイルをオープンしてファイルディスクリプタを取得し、使い終わったらクローズする、ってわけだ。
ただし、標準入出力に関しては最初から os パッケージに用意されている。

  • os.Stdin: 標準入力
  • os.Stdout: 標準出力
  • os.Stderr: 標準エラー出力

標準入力から読み込むには os.Stdin.Read 関数、出力するには os.Stdout.Write または os.Stderr.Write 関数を使う。
ちょっと試してみよう。次のプログラムは、標準入力から受け取った文字列をそのまま標準出力に書き出す(ctrl + C で終了)。

package main

import "os"

func main() {
    buff := make([]byte, 256)
    for {
        c, _ := os.Stdin.Read(buff)
        if c == 0 { break }
        os.Stdout.Write(buff[:c])
    }
}
^o^ > go run echo.go
hello
hello

ひとつめの hello がキーボードからの入力で、ふたつめの hello が返ってきた出力だ。

二分探索木

今日は、練習のため二分探索木の写経をする。

cf. 二分探索木 – M.Hiroi’s Home Page お気楽 Go 言語プログラミング入門

package main

import "fmt"

type Item interface {
    Eq(Item) bool
    Less(Item) bool
}

type Node struct {
    item Item
    left, right *Node
}

func newNode(x Item) *Node {
    p := new(Node)
    p.item = x
    return p
}

func searchNode(node *Node, x Item) bool {
    for node != nil {
        switch {
            case x.Eq(node.item): return true
            case x.Less(node.item): node = node.left
            default: node = node.right
        }
    }
    return false
}

func insertNode(node *Node, x Item) *Node {
    switch {
        case node == nil: return newNode(x)
        case x.Eq(node.item): return node
        case x.Less(node.item):
            node.left = insertNode(node.left, x)
        default:
            node.right = insertNode(node.right, x)
    }
    return node
}

func searchMin(node *Node) Item {
    if node.left == nil {
        return node.item
    }
    return searchMin(node.left)
}

func deleteMin(node *Node) *Node {
    if node.left == nil {
        return node.right
    }
    node.left = deleteMin(node.left)
    return node
}

func deleteNode(node *Node, x Item) *Node {
    if node != nil {
        if x.Eq(node.item) {
            if node.left == nil {
                return node.right
            } else if node.right == nil {
                return node.left
            } else {
                node.item = searchMin(node.right)
                node.right = deleteMin(node.right)
            }
        } else if x.Less(node.item) {
            node.left = deleteNode(node.left, x)
        } else {
            node.right = deleteNode(node.right, x)
        }
    }
    return node
}

func foreachNode(f func(Item), node *Node) {
    if node != nil {
        foreachNode(f, node.left)
        f(node.item)
        foreachNode(f, node.right)
    }
}

type Tree struct {
    root *Node
}

func newTree() *Tree {
    return new(Tree)
}

func (t *Tree) searchTree(x Item) bool {
    return searchNode(t.root, x)
}

func (t *Tree) insertTree(x Item) {
    t.root = insertNode(t.root, x)
}

func (t *Tree) deleteTree(x Item) {
    t.root = deleteNode(t.root, x)
}

func (t *Tree) foreachTree(f func(Item)) {
    foreachNode(f, t.root)
}

func (t *Tree) printTree() {
    t.foreachTree(func(x Item) { fmt.Print(x, " ") })
    fmt.Println("")
}

type Int int

func (n Int) Eq(m Item) bool {
    return n == m.(Int)
}

func (n Int) Less(m Item) bool {
    return n < m.(Int)
}

func main() {
    a := newTree()
    for _, v := range []int { 5,6,4,7,3,8,2,9,1,0 } {
        a.insertTree(Int(v))
    }
    a.printTree()
    for i := 0; i < 10; i++ {
        fmt.Println(a.searchTree(Int(i)))
    }
    a.printTree()
    for i := 0; i < 10; i++ {
        a.deleteTree(Int(i)) a.printTree()
    }
}

探索木自体は Item 型(インターフェイス)を対象としている。このため intInt という別名をつけて EqLess の2つのメソッドを定義している。わざわざ別名をつけているのは、通常の(構造体でない)型にはメソッドを定義できないため。

^o^ > go run binarytree.go
0 1 2 3 4 5 6 7 8 9
true
true
true
true
true
true
true
true
true
true
0 1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
2 3 4 5 6 7 8 9
3 4 5 6 7 8 9
4 5 6 7 8 9
5 6 7 8 9
6 7 8 9
7 8 9
8 9
9

インターフェイスの埋め込み

構造体と同じく、インターフェイスも別のインターフェイスに埋め込むことができる。
下の例では、BazIFooIBarI を埋め込んでいる。すると、BazI を実装した構造体 Baz では、FooIBarI のメソッドを、あたかも自分のメソッドのように使えるようになる。

package main

import "fmt"

type Foo struct {
    a int
}

type FooI interface {
    getA() int
}

func (p *Foo) getA() int {
    return p.a
}

type Bar struct {
    b int
}

type BarI interface {
    getB() int
}

func (p *Bar) getB() int {
    return p.b
}

type Baz struct {
    Foo
    Bar
}

type BazI interface {
    FooI
    BarI
}

func main() {
    a := []FooI{
        &Foo{1},
        &Foo{2},
        &Baz{},
    }
    b := []BarI{
        &Bar{10},
        &Bar{20},
        &Baz{},
    }
    c := []BazI{
        &Baz{},
        &Baz{Foo{1}, Bar{2}},
        &Baz{Foo{3}, Bar{4}},
    }
    for i := 0; i < 3; i++ {
        fmt.Println(a[i].getA())
        fmt.Println(b[i].getB())
        fmt.Println(c[i].getA())
        fmt.Println(c[i].getB())
    }
}
^o^ > go run interface_embed.go
1
10
0
0
2
20
1
2
0
0
3
4