Skip to content

Instantly share code, notes, and snippets.

@suzuken
Last active August 13, 2018 02:01
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save suzuken/d892c42e56c986bca813 to your computer and use it in GitHub Desktop.
Save suzuken/d892c42e56c986bca813 to your computer and use it in GitHub Desktop.

tour of go

2015/11/18: 社内勉強会用に解説を作成。動作環境はGo 1.5.1 (darwin/amd64)。

Exercise: Loops and Functions

ニュートン法の問題。

package main

import (
    "fmt"
)

func Sqrt(x float64) float64 {
}

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

とりあえず10回ループするとこんな感じ。

package main

import (
	"fmt"
)

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

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

適当にしきい値をもうけてやる方式だとこんな感じ

package main

import (
	"fmt"
	"math"
)

func Sqrt(x float64) float64 {
	z := 1.0
	delta := 0.001
	for {
		next := z - (z*z-x)/2*z
		if math.Abs(next-z) < delta {
			break
		}
		z = next
	}
	return z
}

func main() {
	fmt.Println(Sqrt(2))
	// 1.4137132974342435
	fmt.Println(math.Sqrt(2))
	// 1.4142135623730951
}

Exercise: Slices

https://tour.golang.org/moretypes/15

画像バイナリを生成する問題

package main

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

func Pic(dx, dy int) [][]uint8 {
}

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

x軸y軸の各座標の濃さを指定するとブルースケールで表示される。(まあグレースケールのブルー版)

package main

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

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

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

ちなみに pic.Show() 実装はこうなってる tour/pic.go at master · golang/tour

Exercise: Maps

https://tour.golang.org/moretypes/20

それぞれの単語をカウントできるようにしてね、という問題

package main

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

func WordCount(s string) map[string]int {
	return map[string]int{"x": 1}
}

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

このまま実行するとエラーがでる

FAIL
 f("I am learning Go!") =
  map[string]int{"x":1}
 want:
  map[string]int{"Go!":1, "I":1, "am":1, "learning":1}

strings.Fields が便利だよ、って書いてあるのでみてみましょう。

-> % go doc strings.Fields
func Fields(s string) []string

    Fields splits the string s around each instance of one or more consecutive
    white space characters, as defined by unicode.IsSpace, returning an array of
    substrings of s or an empty list if s contains only white space.

ソースからみてもいいですね。

// Fields splits the string s around each instance of one or more consecutive white space
// characters, as defined by unicode.IsSpace, returning an array of substrings of s or an
// empty list if s contains only white space.
func Fields(s string) []string {
	return FieldsFunc(s, unicode.IsSpace)
}

こんな感じでよさそうですね。

package main

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

func WordCount(s string) map[string]int {
	m := make(map[string]int)
	for _, f := range strings.Fields(s) {
		m[f] += 1
	}
	return m
}

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

ちゃんと通りました。

PASS
 f("I am learning Go!") = 
  map[string]int{"I":1, "am":1, "learning":1, "Go!":1}
PASS
 f("The quick brown fox jumped over the lazy dog.") = 
  map[string]int{"quick":1, "fox":1, "lazy":1, "dog.":1, "The":1, "brown":1, "jumped":1, "over":1, "the":1}
PASS
 f("I ate a donut. Then I ate another donut.") = 
  map[string]int{"another":1, "I":2, "ate":2, "a":1, "donut.":2, "Then":1}
PASS
 f("A man a plan a canal panama.") = 
  map[string]int{"panama.":1, "A":1, "man":1, "a":2, "plan":1, "canal":1}

Exercise: Fibonacci closure

みんな大好きフィボナッチです。無名関数で返しましょう。

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
}

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

ちなみに func() intfibonacci() の戻りの型に指定されています。 int を戻す手続きを返せば良いので、例えば以下のような形式を期待します。

func fibonacci() func() int {
	return func() int { return 0 }
}

buildは正常に通ります。まあもちろんこれは結果として0を返してしまいます。では実装します。

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
	x := 0
	y := 1
	return func() int {
		x, y = y, x+y
		return x
	}
}

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

ということでクロージャと局所変数をうまく使えばよいですね。

Exercise: Stringers

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.

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

普通に実行すると下のようになります。

loopback: [127 0 0 1]
googleDNS: [8 8 8 8]

IPアドレスv4なんで、 127.0.0.1 と表示したいですね。

-> % go doc fmt.Stringer
type Stringer interface {
        String() string
}

    Stringer is implemented by any value that has a String method, which defines
    the ``native'' format for that value. The String method is used to print
    values passed as an operand to any format that accepts a string or to an
    unformatted printer such as Print.

Stringer というのは String() string をもつ型に対するinterfaceになっています。つまり String() stringIPAddr について実装すれば、 fmt.Printf などで String() string をつかってくれるわけです。あとで内部実装もみてみましょう。

package main

import "fmt"

type IPAddr [4]byte

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

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

これで以下のように出力できます。 fmt.Sprintf 便利ですね。

loopback: 127.0.0.1
googleDNS: 8.8.8.8

Exercise: Errors

package main

import (
    "fmt"
)

func Sqrt(x float64) (float64, error) {
    return 0, nil
}

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

平方根だすときに負数わたされたらerror返すようにしましょう。(複素数はサポートしない、ということにします。)

package main

import (
	"fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %g", e)
}

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

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

Sqrt本体の実装は割愛します。Error() string を持っているtypeは error 型とみなされます。出力は以下のとおり。

0 <nil>
0 cannot Sqrt negative number: -2

Exercise: Readers

package main

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

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.

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

io.Reader をつかいます。 io.Reader のインタフェースを実装すると、Goの全てのIOについてReadの機能を提供できます。

package main

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

type MyReader struct{}

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

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

Exercise: rot13Reader

package main

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

type rot13Reader struct {
    r io.Reader
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

この問題はさっきのio.Readerのところの応用です。渡ってきた文字列をいい感じに変換して、rot13になってるものを普通に読めるようにしよう、ということですね。

ROT13 - Wikipedia

rot13というのは a->n, b->o, c->p のような規則で文字を変換するという簡単な暗号です。

package main

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

type rot13Reader struct {
	r io.Reader
}

func (rot *rot13Reader) Read(b []byte) (int, error) {
	n, err := rot.r.Read(b)
	for i := 0; i < len(b); i++ {
		if ('A' <= b[i] && b[i] <= 'M') || ('a' <= b[i] && b[i] <= 'm') {
			b[i] = b[i] + 13
		} else if ('N' <= b[i] && b[i] <= 'Z') || ('n' <= b[i] && b[i] <= 'z') {
			b[i] = b[i] - 13
		}
	}
	return n, err
}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}

You cracked the code! 、となります。ちなみに ' でくくると rune になります。runeというのはstringではなく、文字のことをしめします。つまり、 string は文字列であり、 rune は文字ですから、内部的には文字の配列がstring、となります。

Strings, bytes, runes and characters in Go - The Go Blog

ちなみにこのエクササイズでrot13Readerを書きましたが、rot13Writerも同じ要領でかけますね。

Exercise: HTTP Handler

https://tour.golang.org/methods/14

package main

import (
    "log"
    "net/http"
)

func main() {
    // your http.Handle calls here
    log.Fatal(http.ListenAndServe("localhost:4000", nil))
}

struct つくって ServeHTTP を実装しようという問題です。これは手元の環境で走らせるとよいでしょう。

package main

import (
	"fmt"
	"log"
	"net/http"
)

type String string

type Struct struct {
	Greeting string
	Punct    string
	Who      string
}

func (s String) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, s)
}

func (s Struct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "%s%s%s", s.Greeting, s.Punct, s.Who)
}

func main() {
	http.Handle("/string", String("I'm a frayed knot."))
	http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
	// your http.Handle calls here
	log.Fatal(http.ListenAndServe("localhost:4000", nil))
}

以下のようにテストできます。

-> % curl http://localhost:4000/string
I'm a frayed knot.
-> % curl http://localhost:4000/struct
Hello:Gophers!

Exercise: Images

https://tour.golang.org/methods/16

package main

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

type Image struct{}

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

これは image.Image のインタフェースにしたがって自分で Image を実装しましょう、という問題ですね。

ちなみに image パッケージでは image.RGBA なんかがこの image.Image を実装しています。

-> % go doc image RGBA
type RGBA struct {
        // Pix holds the image's pixels, in R, G, B, A order. The pixel at
        // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].
        Pix []uint8
        // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
        Stride int
        // Rect is the image's bounds.
        Rect Rectangle
}

    RGBA is an in-memory image whose At method returns color.RGBA values.

func NewRGBA(r Rectangle) *RGBA
func (p *RGBA) At(x, y int) color.Color
func (p *RGBA) Bounds() Rectangle
func (p *RGBA) ColorModel() color.Model
func (p *RGBA) Opaque() bool
func (p *RGBA) PixOffset(x, y int) int
func (p *RGBA) RGBAAt(x, y int) color.RGBA
func (p *RGBA) Set(x, y int, c color.Color)
func (p *RGBA) SetRGBA(x, y int, c color.RGBA)
func (p *RGBA) SubImage(r Rectangle) Image

ということで問題の指定通りstructをつくっていきましょう。

package main

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

type Image struct {
	width  int
	height int
}

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

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

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

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

出力される画像のグラデーションパターンは At() の実装によって変わることになります。

Exercise: Equivalent Binary Trees

https://tour.golang.org/concurrency/8

package main

import "code.google.com/p/go-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)

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

func main() {
}

ここからconcurrencyの課題です。並行(parallel)ではなく並列です。

Binary Treeは左側のノードのほうが小さい値、右側のノードの方が大きい値をとるようになっています。なので、ツリーをたどっていくときには左側を優先してたどると小さい順に値をとることができる、ということになります。

package main

import (
	"code.google.com/p/go-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) {
	walkIter(t, ch)
	close(ch)
}

func walkIter(t *tree.Tree, ch chan int) {
	if t != nil {
		walkIter(t.Left, ch)
		ch <- t.Value
		walkIter(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 i := range ch1 {
		if i != <-ch2 {
			return false
		}
	}
	return true
}

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

	fmt.Println(Same(tree.New(1), tree.New(1))) // -> true
	fmt.Println(Same(tree.New(1), tree.New(2))) // -> false
}

Exercise: Web Crawler

package main

import (
    "fmt"
)

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
    }
    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 {
        Crawl(u, depth-1, fetcher)
    }
    return
}

func main() {
    Crawl("http://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{
    "http://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "http://golang.org/pkg/",
            "http://golang.org/cmd/",
        },
    },
    "http://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "http://golang.org/",
            "http://golang.org/cmd/",
            "http://golang.org/pkg/fmt/",
            "http://golang.org/pkg/os/",
        },
    },
    "http://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
    "http://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
}

これまでのexerciseのまとめのような問題。

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)
}

var crawled map[string]bool

func childCrawl(url string, depth int, fetcher Fetcher, result chan string, wg *sync.WaitGroup) {
	defer wg.Done()
	if depth <= 0 {
		return
	}

	if e := crawled[url]; e {
		return
	}

	body, urls, err := fetcher.Fetch(url)
	if err != nil {
		result <- err.Error()
		return
	}

	crawled[url] = true

	result <- fmt.Sprintf("found: %s %q", url, body)
	for _, u := range urls {
		wg.Add(1)
		go childCrawl(u, depth-1, fetcher, result, wg)
	}
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
	result := make(chan string)
	var wg sync.WaitGroup

	wg.Add(1)
	go childCrawl(url, depth, fetcher, result, &wg)

	go func() {
		wg.Wait()
		close(result)
	}()
	for r := range result {
		fmt.Println(r)
	}
}

func main() {
	crawled = make(map[string]bool)
	Crawl("http://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{
	"http://golang.org/": &fakeResult{
		"The Go Programming Language",
		[]string{
			"http://golang.org/pkg/",
			"http://golang.org/cmd/",
		},
	},
	"http://golang.org/pkg/": &fakeResult{
		"Packages",
		[]string{
			"http://golang.org/",
			"http://golang.org/cmd/",
			"http://golang.org/pkg/fmt/",
			"http://golang.org/pkg/os/",
		},
	},
	"http://golang.org/pkg/fmt/": &fakeResult{
		"Package fmt",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
	"http://golang.org/pkg/os/": &fakeResult{
		"Package os",
		[]string{
			"http://golang.org/",
			"http://golang.org/pkg/",
		},
	},
}

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment