本文主要记录了在tour.golang.org上练习的相关题目,也学习了一些别的同学相关写法,今天将其总结下来,希望自己能多多提高
sqrt函数
目标是利用牛顿公式实现类似开方功能,牛顿公式为z = z - (zz -x)/(2z)。最开始我写的是这样
package main
import (
"fmt"
)
func Sqrt(x float64) float64{
var z float64 = float64(x)
for i := 0 ;i<10; i++{ //当然这里10也可以改为100或1000等
z = z - (z*z - x)/(2*z)
}
return z
}
func main(){
fmt.Println(Sqrt(2))
}
--------
1.5
1.4375
1.4208984375
1.4161603450775146
1.4147828143349983
1.4143802114005837
1.4142623658001936
1.4142278559705035
1.4142177488197718
1.414214788550556
1.414214788550556
后来,觉得这样写需要有精度控制改为了
package main
import (
"fmt"
"math"
)
func Sqrt(x float64) float64{
z := float64(x)
s := float64(0)
for{
z = z - (z*z - x)/(2*z)
if math.Abs(s-z)<1e-10{ //返回1.414213562439504 ,若改为1e-15则为1.4142135623730963
break
}
s = z
}
return s
}
func main(){
fmt.Println(Sqrt(2))
}
Slices练习
该函数是一个画图函数,要求一个拥有dy长度的切片,且切片中dx的每个元素为一个8位无符号整数。同时告诉我们说,在运行这个程序成功后,会显示一个蓝色的图形,具体图像取决于你使用的功能,比如(x+y)/2,x*y以及x^y。
package main
import (
"golang.org/x/tour/pic"
)
func Pic(dx,dy int) [][]uint8 {
var ret = make([][]uint8 ,dy)
for i := 0;i < dy;i++{
ret[i] = make([]uint8 ,dx)
for j := 0;j < dx;j++ {
ret[i][j] = uint8(dx^dy +(dx+dy)/2)
}
}
return ret
}
func main(){
pic.Show(Pic)
}
Maps练习
该函数实现数单词个数的功能,它应该返回一个包含字符串中单词个数的map.
package main
import (
"golang.org/x/tour/wc"
"strings"
)
func WordCount(s string) map[string]int{
vmap := make(map[string]int)
strs := strings.Fields(s)
length := len(strs)
for i := 0;i<length;i++ {
vmap[strs[i]] =vmap[strs[i]] + 1
//(vmap[strs[i]])++ //看到有同学是这么写的,个人觉得这样写更简洁
}
return vmap
}
func main(){
wc.Test(WordCount)
}
函数练习
学习中隐藏函数作为返回值这块挺有意思,记录了。
package main
import (
"fmt"
)
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main(){
pos,neg := adder(),adder()
for i := 0;i<10;i++ {
fmt.Println(pos(i),neg(-2*i),)
}
}
求解fibonacci数列。
咱们首先看一下fibonacci数列的定义
/ 0 n = 0 f(n) = - 1 n = 1
\ f(n-1)+f(n+2) n >2
正常情况下或教科书上,遇到此类问题常规办法可以采用递归解决,但递归也不是最好的解决办法。下面借鉴了一个取巧的办法。
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())
}
}
Method练习
Go中没有类,但可以在struct类型上定义方法或者包内的任何类型上,但无法定义在一个其他包中已定义的类型上。定义在struct类型上的方法参数来源于receiver。
Method就涉及到interface,这部分可以细致看看。
package main
import (
"fmt"
)
type IPAddr [4]byte
// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr)String() string {
//return fmt.Sprintf("%v.%v.%v.%v",ip[0],ip[1],ip[2],ip[3])
var ipaddr string
ipaddrLen := len(ip)
for i := 0;i < ipaddrLen; i++ {
ipaddr += fmt.Sprintf("%v.",ip[i])
}
ipaddr = ipaddr[:len(ipaddr)-1]
return ipaddr
}
func main(){
addrs := map[string]IPAddr{
"loopback":{127,0,0,1},
"googleDNS":{8,8,8,8},
}
for n,a := range addrs {
fmt.Println("%v: %v\n",n,a)
}
}
Errors练习
error类型是一个interface,即
type error interface {
Error() string
}
只要实现了Error() string方法,也就实现了error接口。
package main
import (
"fmt"
"math"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
return fmt.Sprintf("cannot negative number:%g",float64(e))
}
func Sqrt(x float64) (float64, error) {
if x <= 0 {
return 0 , ErrNegativeSqrt(x)
} else {
z := float64(2.)
s := float64(0)
for {
z = z - (z*z - x)/(2*z)
if math.Abs(s-z) < 1e-15 {
break
}
s = z
}
return s, nil
}
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
Readers
Go标准库中包括了很多实现,例如files,network connections,compressors,ciphers,and others.io.Reader的语法为:
type Reader interface {
func (T) Read(b []byte) (n int,err error)
}
下面是一个读取字符串的例子,每次输出8个字节
package main
import (
"fmt"
"io"
"strings"
)
func main(){
r := strings.NewReader("Hello,Reader!")
b := make([]byte,8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}
输出:
n = 8 err =
下面是实现一个本节的练习
package main
import "golang.org/x/tour/reader"
type MyReader struct{}
func (r MyReader) Read(b []byte) (n int,e error) {
b[0] = 'A'
return 1,nil
}
func main() {
reader.Validate(MyReader{})
}
本节第二个练习时,需要先搞懂rot13算法的含义
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
func (rot *rot13Reader) Read(p []byte) (n int, err error) {
n,err = rot.r.Read(p)
for i := 0; i < len(p); i++ {
if (p[i] >= 'A' && p[i] < 'N') || (p[i] >='a' && p[i] < 'n') {
p[i] += 13
} else if (p[i] > 'M' && p[i] <= 'Z') || (p[i] > 'm' && p[i] <= 'z'){
p[i] -= 13
}
}
return
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
Web Servers
Package http可以适用于任何实现了http.Handler接口的http请求。
package http
type Handler interface {
ServeHttp(w ResponseWriter , r *Request)
}
下面是具体是请求实例
package main
import (
"fmt"
"log"
"net/http"
)
type Hello struct{}
func (h Hello) ServeHTTP(w http.ResponseWriter,r *http.Request) {
fmt.Fprint(w, "Hello!")
}
func main() {
var h Hello
err := http.ListenAndServe("localhost:4000", h)
if err != nil {
log.Fatal(err)
}
}
下面是练习样例
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, "I'm a frayed knot.")
}
func (s Struct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, s.Greeting, s.Punct, s.Who)
}
func main() {
http.Handle("/string", String("I'm a frayed knot."))
http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
log.Fatal(http.ListenAndServe("localhost:4000", nil))
}
Images
Image也是一个接口,具体内容如下:
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
Image使用简单示例
package main
import (
"fmt"
"image"
)
func main() {
m := image.NewRGBA(image.Rect(0, 0, 100, 100))
fmt.Println(m.Bounds())
fmt.Println(m.At(0, 0).RGBA())
}
下面是一个练习Image的示例
package main
import (
"golang.org/x/tour/pic"
"image"
"image/color"
)
type Image struct{
Width,Height int
color uint8
}
func (i *Image) Bounds() image.Rectangle{
return image.Rect(0, 0, i.Width, i.Height)
}
func (i *Image) ColorModel() color.Model {
return color.RGBAModel
}
func (i *Image)At(x,y int) color.Color {
return color.RGBA{i.color+uint8(x), i.color+uint8(y), 255, 255}
}
func main() {
m := Image{100,100,128}
pic.ShowImage(&m)
}
Goroutines
通过Go runtine管理的goroutine是一个轻量级的线程,通过go f(x,y,z)启动一个新的Go程,Goroutines在同样的地址空间运行,因此获取共用的内存时必须同步。sync是很有用的原生包。
channel
channels是通过<-符号进行发送和接收值的管道类型,ch <- v表示发送v到channel ch;v := <-ch表示从ch接收并赋值给局部变量v,箭头表示了数据流的方向。同map与slice一样,channel必须在使用前通过make创建,即 ch := make(chan int)。默认情况下,发送和接收均是阻塞的直到另一端已经就绪。在没有明确的锁及条件变量情况下运行goruntine进行同步,下面channel常规使用示例:
package main
import "fmt"
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // send sum to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
-----------------
-5 17 12 //这是在tour.golang.org中执行的结果
17 -5 12 //这是在自己本机执行的结果
分析:主程序执行a,c声明后,开启第一个分支A和第二个分支B,主程序继续向下执行,发现准备接收c时,
c目前为空,即阻塞进入等待;与此同时进行的分支A与分支B开始执行,返回了不同的c,这里有意思的是,
同样的程序运行时出现了上述两种不一样的结果,自己感觉goruntine无法保证运行结果一致性与机器的运
行速度有关。
示例二
package main
import (
"fmt"
)
var a string
var c = make(chan int)
func fun() {
c <- 0
a = "hello world"
}
func main() {
go fun()
fmt.Println(a)
<-c
}
---------------
输出为空
说明:主线在执行Print时,支线在执行`c <- 0`,所以a还是空的,打印是空.
若将<-c 与 print打印互换,主线是阻塞状态,必须等管道c里有内容后才会继续执行,所以支线的`c <- 0`执行后,主线的`<-c`才会执行,主线`<-c`执行时,支线正在执行`a = "hello world"`然后主线就能正确打印出hello world了。
使用缓存Buffer的格式,buffer的长度为make声明时的第二个初始化参数,ch := make(chan int, 100)。只有buffer被占满时,发送给buffer channel才会被阻塞;只有buffer是空时,接收才会被阻塞。
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println("---分割线---")
fmt.Println(<-ch)
}
------------
1
---分割线---
2
发送者在没有值等待发送时可以关闭channel,接收者可以通过第二个标志符测试是否channel已经关闭,例如 v ,ok := <-ch;若没有更多的值被接收或者channel已经关闭,则ok为false.可以利用for i :=rang c 格式进行循环接收,直到c中没有需要再被传送的值了。只有发送者能关闭channel,接收者不能关闭,若发送过程中关闭channel,会引起一个恐慌panic。channel不像文件,通常情况下你不必关闭它,只有在接收者明确被告知不会有新值被传送时才有必要关闭,例如某个range循环结束。
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
-------------------
0
1
1
2
3
5
8
13
21
34
Select
select可以监听channel上的数据流动,它默认是阻塞的,只有当监听的channel中发送或接收可以进行时才会运行,当多个channel都准备好的时候,将随机选择其一执行。select里面还有default语法,default就是当监听的channel都没有准备好的时候默认执行的。
package main
import (
"fmt"
"strconv"
)
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
fmt.Println("=========")
select {
case c <- x:
fmt.Println("00000000")
x, y = y, x+y
fmt.Println("1111111111")
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println("---i----" + strconv.Itoa(i))
fmt.Println(<-c)
fmt.Println("----j---")
}
quit <- 0
}()
fibonacci(c, quit)
}
-------------------
=========
---i----0
0
----j---
---i----1
00000000
1111111111
=========
00000000
1111111111
=========
1
----j---
---i----2
1
----j---
---i----3
00000000
1111111111
=========
00000000
1111111111
=========
2
----j---
---i----4
3
----j---
---i----5
00000000
1111111111
=========
00000000
1111111111
=========
5
----j---
---i----6
8
----j---
---i----7
00000000
1111111111
=========
00000000
1111111111
=========
13
----j---
---i----8
21
----j---
---i----9
00000000
1111111111
=========
00000000
1111111111
=========
34
----j---
quit
整体理解:
main中执行fibonacci函数时候,select有收发管道数据,顺序执行case时候发数据会阻塞,
知道有读取发的数据,然后继续执行,轮询阻塞,知道收到quit管道数据的时候return
这里为了详细了解该执行过程,在相关地方进行了后台输出。
执行过程说明:主程序执行到fib时,输出=========,这时分支程序输出---i---0,主分支中执行`c<-x`后,
分支程序即可输出0,后又输出---j---,分支程序继续运行,输出---i---1,遇到<-c进入等待,主程序继续
往下执行case中的语句,输出00000000,执行`x, y = y, x+y`,输出1111111111后结束第一次for循环执行;
主程序进入第二次for循环并输出=========,遇到c<-x赋值后继续执行,输出00000000,执行`x, y = y, x+y`
,输出1111111111后,进入第三次for循环输出==========,此时分支程序获得c,输出1,分支程序循环
输出---i---2...
这里的问题是,在第二次for循环遇到c<-x时,为啥分支程序没有及时打印c,而是要输出00000000,
执行`x, y = y, x+y`,输出1111111111后?
select默认default分支示例
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
---------------------
.
.
tick.
.
.
tick.
.
.
tick.
.
.
tick.
.
.
BOOM!
下面是练习啦,估计最开始看的较多同学都是晕的,尽管内容中表明了两个二叉树本质是不一样的,但是若从左侧遍历,结果会发现遍历结果是一致的,这就要求我们去实现一个遍历,并能够比较不同二叉树是否相同。看完我也很晕。后来仔细读提示,你会发现这四步中基本都提示到了,只要有二叉树的基础知识就行。
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){
_walk(t,ch)
close(ch)
}
func _walk(t *tree.Tree, ch chan int){
if t != nil {
_walk(t.Left, ch)
ch <- t.Value
_walk(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() {
var ch = make(chan int)
go Walk(tree.New(1),ch)
for v := range ch {
fmt.Println(v)
}
fmt.Println(Same(tree.New(1), tree.New(1)))
fmt.Println(Same(tree.New(1), tree.New(2)))
}
另一个练习是利用go程实现一个web爬虫程序,要求修改Crawl功能,获取URL的内容,并避免相同URL被执行两次(这个练习我当时并未吃透,将前辈们的答案放在这里,先Mark下吧)
tckage 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 store map[string]bool
var wg sync.WaitGroup
func _crawl(url string, depth int, fetcher Fetcher, Results chan string, wg *sync.WaitGroup) {
defer wg.Done()
if depth <= 0 {
return
}
if exists := store[url]; exists {
return
}
body, urls, err := fetcher.Fetch(url)
if err != nil {
Results <- fmt.Sprintf("not found: %s", url)
return
}
store[url] = true
Results <- fmt.Sprintf("found: %s %q", url, body)
for _, u := range urls {
wg.Add(1)
go _crawl(u, depth-1, fetcher, Results, wg)
}
}
// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
Results := make(chan string)
wg.Add(1)
go _crawl(url, depth, fetcher, Results, &wg)
go func() {
wg.Wait()
close(Results)
}()
for r := range Results {
fmt.Println(r)
}
}
func main() {
store = 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/",
},
},
}