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