Yabu.log

ITなどの雑記

a tour of Goをやった

Goならわかるシステムプログラミングで推奨していたのでとりあえず一周して見た*1

https://go-tour-jp.appspot.com/list

Go言語の文法については、全くのプログラミング初心者でなければ、tour of Goという公式のチュートリアルを一通りこなすだけで、本書の範囲なら十分理解できるでしょう

Goならわかるシステムプログラミングp4より抜粋

と書かれていたのでとりあえずやっておこう、というつもりで進めたのだが。。。 だらだらやったのもあってか1.5日ほどかかった。

とりあえず自分は日頃競プロ的な頭を使うプログラミングをやらないので、 プログラミング能力が劇的に落ちていることを実感した。

時間をかけて競プロを強めようとは思わないが、アルゴリズムとかのエンジニアとしての基礎的な部分を固める必要があると再度認識した。

コードはあまり人に見せられるような綺麗なものではないけど、時間がかかった課題のものを中心に公開しておきます。

フィボナッチ

https://go-tour-jp.appspot.com/moretypes/26

DRYにものすごく反しているような???

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
    count := 0
    v1 := 0
    v2 := 0
    value := 0
    return func() int {
        defer func() { count++ }()
        if count == 0 {
            return 0
        } else if count == 1 {
            v1 = 0
            v2 = 1
            return 1
        } else {
            value = v1 + v2
            v1 = v2
            v2 = value
            return value
        }
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 15; i++ {
        fmt.Println(f())
    }
}

ワードカウント?的なやつ。一応テストは全てパスした

https://go-tour-jp.appspot.com/moretypes/23

package main

import (
    "golang.org/x/tour/wc"
    "strings"
)

func WordCount(s string) map[string]int {
    wordlists := strings.Fields(s)
    countMap := make(map[string]int)
    for _, v := range wordlists {
        countMap[v]++
    }
    //return map[string]int{"x": 1}
    return countMap
}

func main() {
    wc.Test(WordCount)
}

なんか画像を作るやつ

https://go-tour-jp.appspot.com/moretypes/18

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
    value := make([][]uint8, dy)
    for i := 0; i < dy; i++ {
        value[i] = make([]uint8, dx)
        for j:=0;j<dx;j++{
            value[i][j] = uint8(i^j)
        }
    }
    return value
}

func main() {
    pic.Show(Pic)
}

生成する画像は、好きに選んでください。例えば、面白い関数に、 (x+y)/2 、 x*y 、 xy などがあります。

f:id:yuyubu:20180918023900p:plain
(x+y)/2
f:id:yuyubu:20180918023856p:plain
x * y
f:id:yuyubu:20180918023902p:plain
x ^ y

ASCII文字 'A' の無限ストリームを出力する Reader

package main

import "golang.org/x/tour/reader"

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func(r MyReader) Read(b []byte) (int ,error){
    b[0] = 'A'
    return 1,nil
}


func main() {
    reader.Validate(MyReader{})
}

io.Readerをラップした暗号復号処理(rot13)

rot13Reader(io.Readerをラップして換字式暗号を標準入出力に変換してだす)

readerが最後にerrorとしてio.EOFを返すのが解らずに、1時間ほど試行錯誤した。

package main

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

type rot13Reader struct {
    r io.Reader
}

func (r2 rot13Reader) Read(p []byte) (n int, e error) {
    i := 0
    b := make([]byte, 1)
    for {
        _, err := r2.r.Read(b)
        //fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
        //fmt.Printf("b[:n] = %q(%v)\n", b[0], b[0])
        if err == io.EOF {
            break
        }

        value := b[0]
        //文字の場合のみ暗号置換を実施する
        if 64 < b[0] && b[0] < 91 {
            value = value + 13
            if value > 90 {
                value = value - 26
            }
        }

        if 96 < b[0] && b[0] < 123 {
            value = value + 13

            if value > 122 {
                value = value - 26
            }
        }

        //fmt.Printf("lot13 = %q(%v)\n", value, value)
        p[i] = value
        i++
    }

    return i, io.EOF
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    //b := make([]byte, 100);r.Read(b)
    io.Copy(os.Stdout, &r)
}

解読後は普通の文章になっているので多分あっていると思うが、if文の境界値条件はきちんと検査していないのでバッグっているかもしれない。

conncurenncyを使った二分木探索

https://go-tour-jp.appspot.com/concurrency/8

package main

import (
    "fmt"
    "golang.org/x/tour/tree"
)

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    //fmt.Println(t)
    //左があるときはどんどん左に行っちゃう
    if t.Left != nil {
        Walk(t.Left, ch)
        ch <- t.Value
        if t.Right != nil {
            Walk(t.Right, ch)
        }
        //左がなくなったけど右がある場合はそっちに行く
    } else if t.Right != nil {
        ch <- t.Value
        Walk(t.Right, ch)
    } else {
        //葉
        ch <- t.Value
    }
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
    ch1:=make(chan int,10)
    ch2:=make(chan int,10)
    Walk(t1,ch1)
    Walk(t2,ch2)

    for i:=0;i<10;i++{
        v1:= <-ch1
        v2:= <-ch2
        if(v1!=v2){
            return false
        }
    }
    return true

}

func main() {
    fmt.Println("test")
    /*

   ch := make(chan int)
   go Walk(tree.New(2), ch)
   count := 0
   for i := range ch {
       fmt.Println(i*10)
       count++
       if(count == 10){
           //closeするタイミングをどこに入れるか解らなかったので
           //呼び出し側にいれた。こういうプログラミングは良くないと思う
           close(ch)
       }
   }
   */
    b1:= Same(tree.New(1), tree.New(2))
    fmt.Println(b1)
}

mutexを使ったクローラー

https://go-tour-jp.appspot.com/concurrency/10

Crawlメソッドの呼び出しを並列に(goroutine)にしなければ動く。 並列にするとなぜか正しく動かない。これは自分がmutexとか並列プログラミングにまだまだ無知だからだと思う。 とりあえず私が並列プログラミングをやるとdeadlockだらけになることがわかった。

package main

import (
    "fmt"
    "sync"
)

type Fetcher interface {
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
    // TODO: Fetch URLs in parallel.
    // TODO: Don't fetch the same URL twice.
    // This implementation doesn't do either:

    if depth <= 0 {
        return
    }

    mux.Lock()
    if check[url] == 1 {
        mux.Unlock()
        return
    }
    check[url] = 1
    body, urls, err := fetcher.Fetch(url)
    mux.Unlock()


    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("found: %s %q\n", url, body)
    for _, u := range urls {
        //go Crawl(u, depth-1, fetcher) //並列だと正しく動かない
    Crawl(u, depth-1, fetcher)

    }
    return
}

func main() {
    check = make(map[string]int)
    Crawl("https://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
    body string
    urls []string
}

var mux sync.Mutex
var check map[string]int

func (f fakeFetcher) Fetch(url string) (string, []string, error) {

    if res, ok := f[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
    "https://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "https://golang.org/pkg/",
            "https://golang.org/cmd/",
        },
    },
    "https://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "https://golang.org/",
            "https://golang.org/cmd/",
            "https://golang.org/pkg/fmt/",
            "https://golang.org/pkg/os/",
        },
    },
    "https://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "https://golang.org/",
            "https://golang.org/pkg/",
        },
    },
    "https://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "https://golang.org/",
            "https://golang.org/pkg/",
        },
    },
}

他:違和感のある翻訳

呼び出す 具体的な メソッドを示す型がインターフェースのタプル内に存在しないため、 nil インターフェースのメソッドを呼び出すと、ランタイムエラーになります。

https://go-tour-jp.appspot.com/methods/13

編集履歴を辿って原文を確認すると以下のようであった

Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.

https://github.com/atotto/go-tour-jp/commit/123f593e8270035b7f59a8d4eaa0e61bbee98239

具象メソッドを含むinterfaceタプルが存在しないのでnilインターフェースの呼び出しはエラーになります。くらいが妥当な気がする?

というかinterfaceタプル、ってなに?っていう感じでgoをそこまでわかっていないので具体的に今どういう役が適切かは提案できない。

https://qiita.com/umisama/items/e215d49138e949d7f805

だたgoのnilJavaのnullとも違うし、nil自体がnil型のような特性を持っているような振る舞いがあるようなので、 この辺りは今後注意して学習して行きたい。

感想

今までは写経中心だったり、ロジックというよりもAPIのドキュメントを調べて書かれている通りに実装することが中心だったが、Goできちんとプログラミングを書くのは初めてだったのでいい経験になったと思う。

何よりも、tour of Goをやらねばいけない、という憑き物のようなものが落とせてよかった。

*1:と行っても周回するつもりはあいrません