这两天学完了 A Tour of Go 官方的语法教学,里面有不少的 Excercise(训练题)。但愿对你们有用,若是有其余人也写过,并以为我写的不对的,求教!❤️golang
x
,咱们经过 loop 和 function 来找到其平方根 z
,即 z² = x
package main
import (
"fmt"
"math"
)
func Sqrt(x float64) float64 {
z := x/2
for i:= 0; math.Abs(z*z - x) > 0.0000000001; i++ {
z -= (z*z - x) / (2*z)
fmt.Println(i, "z:", z, "z^2 -x:", z*z - x)
}
return z
}
func main() {
fmt.Println(Sqrt(1000))
}
复制代码
z := x/2
这个是猜想的初始值(也能够像是题目里的 hint 写的设置成 1)math.Abs(z*z - x) > 0.0000000001
用最优解的逻辑就是给了一个 tolerance 0.0000000001
,即咱们用计算公式算出来的 z²
与 x
的差值已经足够小,咱们认定预估的 z
算是一个近似准确值。Pic
函数来生成一个 [][]uint8
的 2D 图片(便可说是 Array of Array)。它的大小由参数 (dx, dy int)
决定,这个有 dy
个数组,每一个数组里又有一个长度为 dx
的数组。而相关的位置上 pic[y][x]
是这个图片的 bluescale(只有蓝色)数值,格式为 uint8
。package main
import "golang.org/x/tour/pic"
func Pic(dx, dy int) [][]uint8 {
pic := make([][]uint8, dy)
for i := range pic {
pic[i] = make([]uint8, dx)
for j := range pic[i] {
pic[i][j] = uint8(i*j + j*j)
}
}
return pic
}
func main() {
pic.Show(Pic)
}
复制代码
pic := make([][]uint8, dy)
先建一个数组,长度是 dy
,数组里每一个元素的内容是一个数组 []uint8
pic[i] = make([]uint8, dx)
在数组里的第 i
个元素里,咱们再创造一个 []uint8
数组,长度为 dx
pic[i][j] = uint8(i*j + j*j)
表示咱们设计的 bluesacle 计算公式里,pic[i][j]
位置的数值是 uint8(i*j + j*j)
(这里你能够随意改几个,能看到不少不一样的效果哦!)WordCount
,它能够回复一个 map
里面包含输入字符串中出现的单词 word 及相应出现的次数。map[string]int{"I":1, "love":1, "you":3}
package main
import (
"golang.org/x/tour/wc"
"strings"
)
func WordCount(s string) map[string]int {
m := make(map[string]int)
words := strings.Fields(s)
for _, word := range words {
m[word] = m[word] + 1
}
return m
}
func main() {
wc.Test(WordCount)
}
复制代码
strings.Files(s)
这个函数会自动切分一个字符串到一个数组,每一个数组里是一个 wordmap
而后在数组里每当某一个 word
出现,就相应的增长 1fibonacci
函数,使其返回一个函数package main
import "fmt"
// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
a, b := 0, 1
return func() int {
c := a
a, b = b, a+b
return c
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
复制代码
func fibonacci() func() int
中,返回的是一个函数 func() int
,而这个函数每次运行返回的是一个 int
fibonacci()
中,变量 a
和 b
定义在函数 fibonacci()
里,并被此函数返回的 return func() int { ... }
函数引用到,也就是说在返回的函数里 a
和 b
两个变量一直存储在内存中,且数值会一直变化f := fibonacci()
中 f
是 fibonacci()
返回的函数,在初始状况中,此时的 a, b := 0, 1
f()
调用为例:
c := a
: c
赋值为 a
即 0a, b = b, a+b
:a
赋值为 b
即 1,b
赋值为 a+b
即 1return c
,返回 0,也就是斐波那契数列的第一个值f()
调用,注意此时 a
是 1,b
是 1:
c := a
: c
赋值为 a
即 1a, b = b, a+b
:a
赋值为 b
即 1,b
赋值为 a+b
即 2return c
,返回 1,也就是斐波那契数列的第二个值f
函数被调用了 10 次,输出了斐波那契数列前 10 个值type IPAddr [4]byte
增长 Stringer Interface 函数来输出字符串,即 IPAddr{1, 2, 3, 4}
print 为 1.2.3.4
package main
import (
"fmt"
"strings"
"strconv"
)
type IPAddr [4]byte
// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string {
s := make([]string, len(ip))
for i, val := range ip {
s[i] = strconv.Itoa(int(val))
}
return fmt.Sprintf(strings.Join(s, "."))
}
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)
}
}
复制代码
strconv.Itoa
,int to stringIPAddr
是一个大小为 4 的 []byte
,咱们生成一个 [4]string
数组 s
,每个元素是 IP 地址中的一位,而且咱们用 strconv.Itoa(int(val))
把它转换为字符串strings.Join(s, ".")
将字符串数组用 "."
连起来fmt.Printf("%v: %v\n", name, ip)
时,会对 type IPAddr
默认调用其 Stringer interface
下定义的 String()
函数来输出sqrt
函数,当参数是一个负数时,添加 type ErrNegativeSqrt float64
,并经过定义 func (e ErrNegativeSqrt) Error() string
从而使其为 error
package main
import (
"fmt"
"math"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}
func Sqrt(x float64) (float64, error) {
if (x > 0) {
z:= x/2
for i:= 0; math.Abs(z*z - x) > 0.0000000001; i++ {
z -= (z*z - x) / (2*z)
fmt.Println(i, "z:", z, "z^2 -x:", z*z - x)
}
return z, nil
} else {
return 0, ErrNegativeSqrt(x)
}
}
复制代码
error
类型是一个 built-in interface,须要定义 Error() string
函数,测试一个 error
type 是不是 nil
是定义函数返回是否出错的方法。例如: i, err := strconv.Atoi("42")
返回值中 i
表明函数返回数值,而 err
若是不是 nil
的话,则表示有错误发生func (e ErrNegativeSqrt) Error() string
定义了 ErrNegativeSqrt
属于 error
的 Error()
函数,也就简洁说明 ErrNegativeSqrt
是一个 error
func Sqrt(x float64) (float64, error)
函数返回两个数值,前者为参数的平方根,后者为 error
,当后者不是 nil
的时候,在 Println
中会自动调用 Error()
输出相应的错误信息字符串。Reader
type,以输出一个无限个 'A'
的字符流package main
import (
"fmt"
"golang.org/x/tour/reader"
)
type MyReader struct{}
type ErrEmptyBuffer []byte
func (b ErrEmptyBuffer) Error() string {
return fmt.Sprintf("cannot read an empty buffer: %v", b)
}
// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (reader MyReader) Read(b []byte) (int, error) {
bLength := len(b)
if (bLength == 0) {
return 0, ErrEmptyBuffer(b)
}
for i := range b {
b[i] = 'A'
}
return bLength, nil
}
func main() {
reader.Validate(MyReader{})
}
复制代码
MyReader
会输出无限个 'A'
,所以只要输入参数 b []byte
不是一个空的 Buffer,就会写满bLength == 0
也就是 Buffer b
是空时,返回 ErrEmptyBuffer(b)
错误'A'
并返回 bLength, nil
rot13Reader
type 使其包含一个 io.Reader
使得其在执行 Read
函数时,会自动根据 rot13
来转化相应的字母字符。package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
func rot13(c byte) byte {
switch {
case (c >= 'A' && c <= 'M') || (c >= 'a' && c <= 'm'):
c += 13
case (c >= 'N' && c <= 'Z') || (c >= 'n' && c <= 'z'):
c -= 13
}
return c
}
func (reader *rot13Reader) Read(b []byte) (n int, err error) {
n, err := reader.r.Read(b)
for i := range b {
b[i] = rot13(b[i])
}
return n, err
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
复制代码
rot13
规则实现函数 func rot13(c byte) byte
,也就是 A-M
与 N-Z
的兑换,以及 a-m
与 n-z
的兑换rot13Reader
的 Read
函数,首先使用期包含的 r
Reader 来读取数据,而后每个数据都经过 rot13
转换,最终返回相应的数字结果image.Image
图像而不单单是一个二维数组数据Image
type,并定义相应的 image.Image
interface,其中
ColorModel
使用 color.RGBAModel
Bounds
使用 image.Rectangle
type,并用 image.Rect(0, 0, w, h)
定义At
会返回具体图片像素点上的颜色,最终用 color.RGBA{v, v, 255, 255}
定义package main
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, y int) color.Color {
v := uint8(x*y + y*y)
return color.RGBA{v, v, 255, 255}
}
func main() {
m := Image{200, 200}
pic.ShowImage(m)
}
复制代码
image.Rect
, color.RGBAModel
, color.RGBA
所以,咱们引入 "image"
和 "image/color"
packagesAt
函数我沿用了以前的图片颜色计算公式 v := uint8(x*y + y*y)
Tree
类型,并实现以下函数来测试两个 Tree
是否存储一样的数列。
Tree
的 Walk
method,使其会按照树内存储数列顺序逐一走完相关数字,方式是逐一传入 ch chan int
中,也就是 func Walk(t *tree.Tree, ch chan int)
,而这个过程放入 goroutine 中完成 go Walk(tree.New(1), ch)
。注意:tree.New(k)
会随机生成一个结构不一的树,但都会存储相同的数列 k
, 2k
, ..., 10k
。Same
函数并调用 Walk
方法,使其能够比较两个 Tree
是否存储相同的数列。例如:Same(tree.New(1), tree.New(1))
应该返回 true
,由于里面都存着 1
, 2
, ... 10
。而 Same(tree.New(1), tree.New(2))
应该返回 false
package main
import (
"fmt"
"golang.org/x/tour/tree"
)
// type Tree struct {
// Left *Tree
// Value int
// Right *Tree
// }
// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
if t.Left != nil {
Walk(t.Left, ch)
}
ch <- t.Value
if t.Right != nil {
Walk(t.Right, ch)
}
}
// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
var v1, v2 int
c1 := make(chan int)
c2 := make(chan int)
go Walk(t1, c1)
go Walk(t2, c2)
for i := 0; i < 10; i++ {
v1 = <-c1
v2 = <-c2
if v1 != v2 {
return false
}
}
return true
}
func main() {
ch := make(chan int)
go Walk(tree.New(10), ch)
for i := 0; i < 10; i++ {
fmt.Println(<-ch)
}
fmt.Println(Same(tree.New(1), tree.New(1)))
}
复制代码
Walk
方法中,咱们按照 t.Left
, ch <- t.Value
,t.Right
顺序逐一将数据传入 ch
Same
方法中,由于咱们知道 tree.New(k)
生成的树包含 10 个节点,所以咱们生成两个 Channels 分别存储两个 Tree
在 Walk
时的数据,而后一个循环 10 次的 Loop 每次从里面分别拿出一个数值,比较相应数列下的数值是否相同,若是出现不一样,则表述两个树不相同。居然能看到这里,那有必要加个微信了 ymkalasoo,咱们在找优秀的 Go 开发者 网页爬虫
Crawl
函数使其能够并行抓去 URL
但不重复抓取fakeFetcher
是一个假的数据集,表示能够用来被测试的爬虫抓取数据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 UrlChecker struct {
urls map[string]bool
mux sync.Mutex
}
func (c *UrlChecker) Crawled(url string) bool {
c.mux.Lock()
if c.urls[url] {
defer c.mux.Unlock()
return true
}
c.urls[url] = true
defer c.mux.Unlock()
return false
}
var uc = UrlChecker{urls: make(map[string]bool)}
// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher, ret chan string) {
// TODO: Fetch URLs in parallel.
// TODO: Don't fetch the same URL twice.
// This implementation doesn't do either:
defer close(ret)
if depth <= 0 {
return
}
if uc.Crawled(url) {
return
}
body, urls, err := fetcher.Fetch(url)
if err != nil {
fmt.Println(err)
return
}
ret <- fmt.Sprintf("found: %s %q\n", url, body)
results := make([]chan string, len(urls))
for i, u := range urls {
results[i] = make(chan string)
go Crawl(u, depth-1, fetcher, results[i])
}
for _, result := range results {
for s := range result {
ret <- s
}
}
return
}
func main() {
result := make(chan string)
go Crawl("https://golang.org/", 4, fetcher, result)
for s := range result {
fmt.Println(s)
}
}
// 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/",
},
},
}
复制代码
UrlChecker
是用来测试一个 url
是否已经被 fetch
过,其中包含一个 urls map[string]bool
存储 URL 抓取状况,另 mux sync.Mutex
来防止数据被重复修改,进而不会出现,fetcher 在 并行的时候,由于此时某一个 URL 都没有在 UrlChecker
被标注 fetch 过,进而同时 fetch。func (c *UrlChecker) Crawled(url string) bool
来存储一个 URL 已经被抓取的状态。在测试过程当中 c.mux.Lock()
使得,此段数据被阻断其余 goroutine 修改。 defer c.mux.Unlock()
意思是在 return
后再执行解开 Mutual Exclusion 锁。var uc = UrlChecker{urls: make(map[string]bool)}
,当 uc.Crawled(url)
是 true
的时候,再也不抓取相应 url。func Crawl(url string, depth int, fetcher Fetcher, ret chan string)
最后一个参数 ret chan string
传入一个 channel 来存储抓取结果results := make([]chan string, len(urls))
,每个 url 下的须要更深一层抓取的 urls 生成相应的多个 channels,并逐一抓取go Crawl(u, depth-1, fetcher, results[i])
循环抓取 urls复制代码
for _, result := range results { for s := range result { ret <- s } } ``` 每个抓取到的数据从相应的 channel s
传入最上层的 channel ret
,完成全部的抓取数组
最终的输出结果:bash
not found: https://golang.org/cmd/
found: https://golang.org/ "The Go Programming Language"
found: https://golang.org/pkg/ "Packages"
found: https://golang.org/pkg/fmt/ "Package fmt"
found: https://golang.org/pkg/os/ "Package os"
复制代码
辛苦啦,我也在学 Go 哦!微信