typeof Diary

VimとかJSとか。やったことのメモ。自分のため。

A Tour of Goを完走した

1周目はよくわからなかったけど、よく読んで2周目やってみたら意外と解けました。

ということで、一通りやってみたコードのまとめ。
もっとスマートにできたりすると思う。

Loops and Functions

関数とループを使った簡単な練習として、平方根の計算を実装してみましょう: 数値 x が与えられたときに z² が最も x に近い数値 z を求めたいと思います。

func Sqrt(x float64) float64 {
    z := 1.0
    for i := 0; i < 10; i++ {
        z -= (z*z - z) / (2 * z)
    }
    return z
}

次に値が変化しなくなった (もしくはごくわずかな変化しかしなくなった) 場合にループを停止させます。 それが 10 回よりも多いか少ないかを確認してください。

この文章が本当に理解できず、悩みました。
「ごくわずかな変化ってなんやねん」って感じで・・・。

下の方にニュートン法のことが触れられていて、そっちを読むとなんとなく理解し始めました。

func Sqrt(x float64) float64 {
    z := 1.0
    var prev float64
    for i := 0; i < 10; i++ {
        z -= (z*z - z) / (2 * z)

        if math.Abs(prev-z) < 1e-10 {
            fmt.Printf("%d回目\n" , i+1)
            break
        }

        prev = z
    }
    return z
}

どんなもんなのかを課題解いたよ!って記事のコードいくつか読んでみて噛み砕いてからやりました。
ただ、1e-10のところが実装者によって様々で、1e-101e-60.0001とかいろいろあって、理解していないときは「なんで?」ってなりました。
ここが誤差の許容範囲みたいな感じで理解してます。

Slices

Pic 関数を実装してみましょう。 このプログラムを実行すると、生成した画像が下に表示されるはずです。

ここは問題文通り、+ヒント参考にしながらやればなんとかなりました。

func Pic(dx, dy, int) [][]uint8 {
    pic := make([][]uint8, dy)

    for y, _ := range pic {
        pic[y] = make([]uint8, dx)

        for x, _ := range pic[y] {
            pic[x][y] = uint8(x*y)
        }
    }

    return pic
}

uint8(xxx)のところは、

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

の部分なので、好きな計算式入れてあげると良いです。
画像変わって面白いですよ。なんならここに示されている計算式以外でも変な画像できます。

Maps

WordCount 関数を実装してみましょう。string s で渡される文章の、各単語の出現回数のmapを返す必要があります。 wc.Test 関数は、引数に渡した関数に対しテストスイートを実行し、成功か失敗かを結果に表示します。

一番分かりやすかった気がするのは、普段JSで大体なんでもオブジェクトでKey:Valueにしてなんかしちゃうのが原因?

func WordCount(s string) map[string]int {
    wordList := strings.Fields(s)
 
    m := make(map[string]int)
    for _, word := range wordList {
        if _, exist := m[word]; exist {
            m[word]++
        } else {
            m[word] = 1
        }
    }
 
    return m
}

Fibonacci closure

fibonacci (フィボナッチ)関数を実装しましょう。この関数は、連続するフィボナッチ数(0, 1, 1, 2, 3, 5, ...)を返す関数(クロージャ)を返します。

クロージャの問題。
フィボナッチ数列自体は分かってたのですが、最初の0,1は固定なんですね・・・。

func fibonacci() func() int {
    fib := 0
    result := [2]int{0,0}

    return func() int {
        if fib == 1 {
            result[1] = 1
        } else {
            w := result[1]
            result[1] = result[0] + result[1]
            result[0] = w
        }
        fib++
        return result[1]
    }
}

さすがにもっとスマートに書けるんじゃないかなこれ。

Stringers

IPAddr 型を実装してみましょう IPアドレスをドットで4つに区切った( dotted quad )表現で出力するため、 fmt.Stringer インタフェースを実装してください。 例えば、 IPAddr{1, 2, 3, 4} は、 "1.2.3.4" として出力するようにします。

func (ip IPAddr) String() string {
    return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}

Errors

Sqrt 関数を 以前の演習 からコピーし、 error の値を返すように修正してみてください。 Sqrt は、複素数をサポートしていないので、負の値が与えられたとき、nil以外のエラー値を返す必要があります。

type ErrNegativeSqrt float64

func (x ErrNegativeSqrt) Error() string {
    return fmt.Sprintf("cannot Sqrt negative number %f", float64(x))
}

func Sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, ErrNegativeSqrt(x)
    }

    z := 1.0
    var prev float64
    for i := 0; i < 10; i++ {
        z -= (z*z - z) / (2 * z)

        if math.Abs(prev-z) < 1e-10 {
            fmt.Printf("%d回目\n" , i+1)
            break
        }

        prev = z
    }
    return z, nil
}

Readers

ASCII文字 'A' の無限ストリームを出力する Reader 型を実装してください。

無限ストリームがイマイチピンとこなくて調べました。 ほぼそのままの意味でした()

b[i] = "A"としていてtype errorになって、ここではじめて'A'であることに気付いた。
学び。

otiai10.hatenablog.com

func (r MyReader) Read(b []byte) (int, error) {
    for i := range b {
        b[i] = 'A'
    }
    return len(b), nil
}

rot13Reader

io.Reader を実装し、 io.Reader でROT13 換字式暗号( substitution cipher )をすべてのアルファベットの文字に適用して読み出すように rot13Reader を実装してみてください。

type rot13Reader struct {
    r io.Reader
}

func (r rot13Reader) Read(b []byte) (n int, err error) {
    n, err = r.r.Read(b)
    if err != nil {
        return
    }

    for i, c := range b[:n] {
        if 'A' <= c && c <= 'Z' {
            b[i] = (c-'A'+13)%26 + 'A'
        } else if 'a' <= c && c <= 'z' {
            b[i] = (c-'a'+13)%26 + 'a'
        }
    }
    return
}

問題文中にあるgzip.NewReaderへのリンクがあったので、コード読んでると、r.r.Readのヒントが得られました。
読んだら書けました。

Image

前に解いた、画像ジェネレーターを覚えていますか? 今回は、データのスライスの代わりに image.Image インタフェースの実装を返すようにしてみましょう。 自分の Image 型を定義し、 インタフェースを満たすのに必要なメソッド を実装し、 pic.ShowImage を呼び出してみてください。

ドキュメント読んだらそれっぽくできました。
とりあえず、w, hだけ持たせて、呼び出しで大きさ決めれるようにはしておきました。

import (
    "golang.org/x/tour/pic"
    "image"
    "image/color"
)

type Image struct {
    w int
    h int
}

func (i Image) ColorModel() color.Model {
    return color.RGBAModel
}
func (i Image) Bounds() image.Rectangle {
    return image.Rect(0, 0, i.w, i.h)
}
func (i Image) At(x int, y int) color.Color {
    return color.RGBA{0, 0, uint8(x ^ y), uint8(x ^ y)}
}

func main() {
    m := Image{180, 180}
    pic.ShowImage(m)
}

Exercise: Equivalent Binary Trees

https://go-tour-jp.appspot.com/concurrency/7
問題文長いので↑で確認してください。。。

Tree.Left, Tree.Rightが*Treeであることに気付けたら、あとは早かったです。

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) {
    recursiveWalk(t, ch)
    close(ch)
}

func recursiveWalk(t *tree.Tree, ch chan int) {
    if t.Left != nil {
        recursiveWalk(t.Left, ch)
    }
    ch <- t.Value
    if t.Right != nil {
        recursiveWalk(t.Right, ch)
    }
}

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

    go Walk(t1, ch1)
    go Walk(t2, ch2)

    for {
        c1, ok1 := <-ch1
        c2, ok2 := <-ch2
        switch {
        case !ok1, !ok2:
            return ok1 == ok2
        case c1 != c2:
            return false
        }
    }
}

func main() {
    ch := make(chan int)
    go Walk(tree.New(1), ch)
    for c := range ch {
        fmt.Println(c)
    }

    fmt.Println(Same(tree.New(1), tree.New(1)))
    fmt.Println(Same(tree.New(1), tree.New(2)))
}

Exercise: Web Crawler

package main

import (
    "fmt"
    "sync"
    "time"
)

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) {
    cacheUrl := make(map[string]int)
    var mux sync.Mutex

    var crawl func(string, int)
    crawl = func(url string, depth int) {
        if depth <= 0 {
            return
        }

        if _, ok := cacheUrl[url]; ok {
            return
        }

        mux.Lock()
        cacheUrl[url]++
        mux.Unlock()
     
        body, urls, err := fetcher.Fetch(url)
        if err != nil {
            fmt.Println(err)
            return
        }

        fmt.Printf("found: %s %q\n", url, body)

        for _, u := range urls {
            go crawl(u, depth-1)
        }

    }

    go crawl(url, depth)
    time.Sleep(time.Second)
    fmt.Println(cacheUrl)
    return
}

func main() {
    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
}

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/",
        },
    },
}

合ってるかわからん!
それっぽいはず・・・。

一通り終えて

コードこれで本当に合っているのかはわからないけど、1回目に書いたものということで残しておきます。

これで、みんなのGo言語を読み進めるスタート地点に立てた気がします。。。   本にもあったけど、また慣れてからやれば、もっと違う感じになると思うので、それはまた追々。

とりあえず、終わったよ報告でした。