元営業WEBエンジニアのアプリ開発日記

営業出身のWEB系エンジニアが気になったものから作ってはメモを残してくブログ

golangのチュートリアル解いてみた

概要

Go言語勉強したい!とりあえずチュートリアル2周読んだ!!
理解度を試すために練習問題(Excesise)解いてメモしとく。
わからないとこはこの方の素晴らしい解答をみてやってみる!!!

練習問題やってみた

Basic

Exercise: Loops and Functions

ニュートン法を用いて平方根求めろって言ってる多分。
ニュートン法自体はこの記事がすごくわかりやすかった!!!
ざっくりいうと「ある関数f(x)が0になるためのxを求める式」はx(n+1) = x(n) - f(x)/f'(x) を繰り返していくことでわかるらしい。

今回の平方根に照らし合わせるとx=√2 <=> 2-x^2=0なのでf(x)=2-x^2が0になるためにはx(n+1) = x(n) - (2-x^2/2x)ってことなんだな。

10回ループして平方根求めてみる

package main

import (
    "fmt"
)

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

func main() {
    fmt.Println(Sqrt(2))
}

何回ループすれば答えに近づくか調べてみる

初期値のzを色々変えてみて、x(n+1)とx(n)の差分が極めて小さくなるまで繰り返す。
初期値1では5回で終わった。100とかにしたら11回だった。
(まぁ無心でmath.Sqrt使っちゃうだろうけど・・・)

package main

import (
    "fmt"
    "math"
)

func Sqrt(x float64) float64 {
    z, count := 1.0, 0
    for {
        prev := z
        z -= (z*z - x) / (2*z)  
        count++
        if math.Abs(z - prev) < 1e-10 {
            break
        }
    }
    fmt.Println("iterate count: ", count)
    return z
}

func main() {
    fmt.Println(Sqrt(2))
}

Exercise: Slices

いい感じのグラデーションが出るように2次元のSlice作って
2次元のインデックを計算するんじゃ!

package main

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

func Pic(dx, dy int) [][]uint8 {
    // 8bit(0-255)数値型の配列をdy個保持する2次元配列生成
    /* イメージ
       [
           []
           [] 
           ...
           [] <- dy個
       ]
   */
    pic := make([][]uint8, dy)
    // []の数(dy)分ループ
    for y := range pic {
        // 8bit(0-255)数値型をdx個保持する配列生成
        /* イメージ
           [0 0 ... 0]<-dx個
       */
        pic[y] = make([]uint8, dx)
        // 0の数(dx)分ループ
        for x := range pic[y] {
            // x,yのインデックスを操作してグラデーションを作成
            /* イメージ
               [(0,0) (0,1) ...   (0,dx-1)]
               [(1,0) (1,1) ...   (1,dx-1)]
               [(2,0) (2,1) ...(dy-1,dx-1)]
           */
            pic[y][x] = uint8((x+y)/2)
            // (x+y)/2の代わりにx*yとかx^yすると色合いがぐっと変わる
        }
    }
    return pic
}

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

Exercise: Maps

文章に出現する文字と数の対応Mapを作るのじゃ!

package main

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

func WordCount(s string) map[string]int {
    // 文字と出現回数格納用のmap生成
    wordCountMap := make(map[string]int)
    // strings.Fieldsで文章を文字で分割
    // 文字数分だけループして出現回数カウント
    for _, word := range strings.Fields(s) {
        wordCountMap[word]++
    }
    return wordCountMap
}

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

Exercise: Fibonacci closure

フィボナッチな数字が帰ってくるようにするのじゃ!

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
    // フィボナッチの2項目(保持される値)
    n, afterN := 0, 1
    return func() int {
        // 計算のために現状の2項目を一時保存
        snapN, snapAfterN := n, afterN
        // 現状の2項目を次の1項目に格納
        // 現状の1項目+2項目を次の2項目に格納
        n, afterN = snapAfterN, snapN + snapAfterN
        // 0から出力されるように一時保存しておいた現状の1項目返却
        return snapN
    }
}

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

Methods and interfaces

Exercise: Stringers

Stringメソッドを実装することで fmt.Printfした時にいい感じ(1.1.1.1)に表示されるようにするのじゃ

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string {
    return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}

func main() {
    hosts := map[string]IPAddr{
        "loopback":  {127, 0, 0, 1},
        "googleDNS": {8, 8, 8, 8},
    }
    for name, ip := range hosts {
        fmt.Printf("%v: %v\n", name, ip)
    }
}

Exercise: Errors

平方根求めるやつでマイナスの数値来た時にError投げるようにする!!
(float64でキャストしないとループしちゃうのなんでや・・・)

package main

import (
    "fmt"
    "math"
)

// エラー用のtype宣言
type ErrorNegativeSqrt float64

// エラー投げられた時に出力されるやつ
func (e ErrorNegativeSqrt) Error() string {
  // float64でキャストしなきゃいけないらしい。Why
    return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}

func Sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, ErrorNegativeSqrt(x)
    }
    z, count := 1.0, 0
    for {
        prev := z
        z -= (z*z - x) / (2*z)  
        count++
        if math.Abs(z - prev) < 1e-10 {
            break
        }
    }
    return z, nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}

Exercise: Readers

Aを無限に出力するものを作りなさい!!

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) {
    for i := range b {
        b[i] = 'A'
    }
    return len(b), nil
}

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

Exercise: rot13Reader

ROT13で文字列を復号するのじゃ!!
ROT13は文字列の順序操作で暗号化する方式(丸パクリ)

package main

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

type rot13Reader struct {
    r io.Reader
}

// io.Copyで読み込む際にこの変換かましてくれる
func (rot13 rot13Reader) Read(b []byte) (int, error) {
    n, err := rot13.r.Read(b)
    for i, v := range b {
        switch {
        case v >= 'A' && v < 'N', v >= 'a' && v < 'n':
            b[i] += 13
        case v >= 'N' && v <= 'Z', v >= 'n' && v <= 'z':
            b[i] -= 13
        }
    }
    return n, err
}

func main() {
  // io.Reader型で文字列取得
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
  // rot13Reader structに設定
    r := rot13Reader{s}
  // io.Copyに参照渡し
    io.Copy(os.Stdout, &r)
}

Exercise: Images

Sliceで生成した画像をimage.Imageインターフェースで実現するんだ!
これも素晴らしい解答の丸パクリだけど、image.Imageに定義されてるインターフェースに必要なメソッドを実装するだけっぽい。。。

package main

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

type Image struct{}

func (i Image) ColorModel() color.Model {
    return color.RGBAModel
}

func (i Image) Bounds() image.Rectangle {
    return image.Rect(0, 0, 256, 256)
}

func (i Image) At(x, y int) color.Color {
    return color.RGBA{uint8(x), uint8(y), 255, 255}
}

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

Concurrency

並行処理ができるみたい。
ちょっと完全に理解しきれてないけどとりあえず解いてみる。
今後いろいろ作りながら適切に理解を深めていこう

Exercise: Equivalent Binary Trees

二分木を用いて並行処理をしてみようみたいなとこ

binary treeを探検して数字を順番に出力

binary treeって左下が自身より小さくて、右下が自身より大きい値で構成されたツリー構造。 ↓こんな感じ。これを並行処理で探検させて1-7を順番に出力する的なこと

      4
  2       6
1   3   5   7

tree.New(k)使うと1*k, 2*k, ..., 10*kのbinary treeが生成されるっぽい

channelとclose使ってみる

チュートリアルにあったchannelとclose使うパターンだとこんな感じ。
rangeでくるくる回して、やりたいこと終わったらclose(ch)って感じ

package main


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


// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    WalkRecrusive(t, ch)
    close(ch)
}


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


// Same determines whether the trees
// t1 and t2 contain the same values.
// func Same(t1, t2 *tree.Tree) bool


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

参考までにだが探索するロジックとしては、こんな感じ
これのイメージが最初全く湧かなかった。再帰的処理に慣れてないのかな・・

1. 頂点(4)を呼び出し walk(tree.New(1), ch)

    1.1. 左下(2)を呼び出し walk(tree.Left, ch)

        1.1.1. 左下(1)を呼び出し walk(tree.Left, ch)

            1.1.1.1. 左下(nil)を呼び出し walk(nil, ch)

                1.1.1.1.1 nilだからreturn if tree == nil return

            1.1.1.2. 自分の値(1)をchに詰め込み  ch <- tree.Value

            1.1.1.3. 右下(nil)を呼び出し  walk(nil, ch)

                1.1.1.3.1. nilだからreturn if tree == nil return

        1.1.2. 自分の値(2)をchに詰め込み  ch <- tree.Value

        1.1.3. 右下(3)を呼び出し walk(tree.Right, ch)
        ...
channelとselectを使ったパターン

せっかくチュートリアルにもあったしselectでも書いてみる
closeじゃなくて、終了用チャネルに値が入ったら終了してる

package main


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


// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int, chend chan bool) {
    WalkRecrusive(t, ch)
    chend <- true
}


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


// Same determines whether the trees
// t1 and t2 contain the same values.
// func Same(t1, t2 *tree.Tree) bool


func main() {
    ch := make(chan int)
    chend := make(chan bool)
    go Walk(tree.New(1), ch, chend)
    for {
        select {
            case val := <- ch:
                fmt.Println(val)
            case <- chend:
                return
        }
    }
}

同じ値を格納したBinary Treeか判定

以下条件を満たしたら異なると判定するように実装 - 一方の探索対象だけなくなる - 1から出力していき、値が異なる場合

package main

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

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    WalkRecrusive(t, ch)
    close(ch)
}

func WalkRecrusive(t *tree.Tree, ch chan int){
    if t == nil {
        return
    }
    WalkRecrusive(t.Left, ch)
    ch <- t.Value
    WalkRecrusive(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 {
        val1, ok1 := <- ch1
        val2, ok2 := <- ch2
        // どちらかチャネルの値が出切ったら終わり
        if !ok1 || !ok2 {
            // 両者falseで数が同じならfalse
            return ok1 == ok2
        }
        
        // 値が異なった時点でfalse
        if val1 != val2 {
            return false
        }
    }
}


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

Exercise: Web Crawler

fekeFetcherで用意したURLたちをクローリングする。
指定されたurlが存在するか検索する。 存在すれば、その配下ににひもづくurlを取得してまた検索という繰り返し

デフォルトから追記したのは以下処理

  • 並行処理
    • Crawlメソッドをgo func内で実行
    • <- doneChでブロッキング
    • 処理が終わればdoneCh <- trueで値突っ込みブロック解除
  • 同じURLを二回検索しない対策
    • fetched.Lock()でロックしてすでに検索済みではないか確認
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)
}

type Fetched struct {
    m map[string]error
    sync.Mutex
}

var fetched Fetched = Fetched{
    m: make(map[string]error),
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
    // 指定した深さ全部終了したら終了
    if depth <= 0 {
        return
    }
 
    // 確認ずみのurlか確認
    fetched.Lock()
    if _, ok := fetched.m[url]; ok {
        fmt.Printf("fetched already: %s\n", url)
        fetched.Unlock()
        return
    }
    fetched.Unlock()
 
    // 対象URLをフェッチ
    body, urls, err := fetcher.Fetch(url)
    fetched.m[url] = err
 
    // 対象URLが存在しなければ終わり
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("found: %s %q\n", url, body)
 
    // 並列処理で配下のurlたちを実行
    doneCh := make(chan bool)
    for _, u := range urls {
        go func(url string){
            Crawl(url, depth-1, fetcher)
            doneCh <- true
        }(u)
        <- doneCh
    }
    return
}

func main() {
    Crawl("https://golang.org/", 4, fetcher)
 
    fmt.Println("----------")
 
    for url, err := range fetched.m {
        if err != nil {
            fmt.Printf("%v failed: %v\n", url, err)
        } else {
            fmt.Printf("%v was fetched\n", url)
        }
    }
}

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