了解标准库是一个非常好的学习语言的方法。接下里的一些章节我们将详细研究标准库中的类型,方法,结合实际应用案例进行有趣的编程。
字符串处理是比较基础的库,我们从简单的库开始入门。
本节我们将继续用实例代码研究以下函数的用法。
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Print(strings.Trim("¡¡¡Hello, Gophers!!!", "!¡")) // Hello, Gophers
}
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
fmt.Print(strings.TrimFunc("¡¡¡Hello, Gophers!!!", func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
}))// Hello, Gophers
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Print(strings.TrimLeft("¡¡¡Hello, Gophers!!!", "!¡")) // Hello, Gophers!!!
}
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
fmt.Print(strings.TrimLeftFunc("¡¡¡Hello, Gophers!!!", func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
})) // Hello, Gophers!!!
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.TrimPrefix("¡¡¡Hello, Gophers!!!", "¡")) // ¡¡Hello, Gophers!!!
fmt.Println(strings.TrimPrefix("¡¡¡Hello, Gophers!!!", "¡¡¡Hello, ")) // Gophers!!!
fmt.Println(strings.TrimPrefix("¡¡¡Hello, Gophers!!!", "¡¡¡Howdy, ")) // ¡¡¡Hello, Gophers!!!
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Print(strings.TrimRight("¡¡¡Hello, Gophers!!!", "!¡")) // ¡¡¡Hello, Gophers
}
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
fmt.Print(strings.TrimRightFunc("¡¡¡Hello, Gophers!!!", func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
}))
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.TrimSpace(" \t\n Hello, Gophers \n\t\r\n")) // Hello, Gophers
}
package main
import (
"fmt"
"strings"
)
func main() {
var s = "¡¡¡Hello, Gophers!!!"
s = strings.TrimSuffix(s, ", Gophers!!!")
s = strings.TrimSuffix(s, ", Marmots!!!")
fmt.Print(s) // ¡¡¡Hello
}
了解标准库是一个非常好的学习语言的方法。接下里的一些章节我们将详细研究标准库中的类型,方法,结合实际应用案例进行有趣的编程。
字符串处理是比较基础的库,我们从简单的库开始入门。
本节我们将继续用实例代码研究以下函数的用法。
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.ToLower("Gopher")) // gopher
}
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "Önnek İş")) // önnek iş
}
package main
import (
"fmt"
"strings"
)
func main() {
// Compare this example to the Title example.
fmt.Println(strings.ToTitle("her royal highness")) // HER ROYAL HIGHNESS
fmt.Println(strings.ToTitle("loud noises")) // LOUD NOISES
fmt.Println(strings.ToTitle("хлеб")) // ХЛЕБ
}
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
fmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "dünyanın ilk borsa yapısı Aizonai kabul edilir"))
// DÜNYANIN İLK BORSA YAPISI AİZONAİ KABUL EDİLİR
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.ToUpper("Gopher"))
}
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "örnek iş")) // ÖRNEK İŞ
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Printf("%s\n", strings.ToValidUTF8("abc", "\uFFFD")) // abc
fmt.Printf("%s\n", strings.ToValidUTF8("a\xffb\xC0\xAFc\xff", "")) // abc
fmt.Printf("%s\n", strings.ToValidUTF8("\xed\xa0\x80", "abc")) // abc
}
了解标准库是一个非常好的学习语言的方法。接下里的一些章节我们将详细研究标准库中的类型,方法,结合实际应用案例进行有趣的编程。
字符串处理是比较基础的库,我们从简单的库开始入门。
本节我们将继续用实例代码研究以下函数的用法。
package main
import (
"fmt"
"strings"
)
func main() {
s := []string{"foo", "bar", "baz"}
fmt.Println(strings.Join(s, ", ")) // foo, bar, baz
}
import (
"fmt"
"strings"
)
func main() {
rot13 := func(r rune) rune {
switch {
case r >= 'A' && r <= 'Z':
return 'A' + (r-'A'+13)%26
case r >= 'a' && r <= 'z':
return 'a' + (r-'a'+13)%26
}
return r
}
fmt.Println(strings.Map(rot13, "'Twas brillig and the slithy gopher...")) // 'Gjnf oevyyvt naq gur fyvgul tbcure...
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println("ba" + strings.Repeat("na", 2)) // banana
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Replace("oink oink oink", "", "###", 1))
fmt.Println(strings.Replace("oink oink oink", "", "###", 2))
fmt.Println(strings.Replace("oink oink oink", "", "###", -1))
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
}
输出:
###oink oink oink
###o###ink oink oink
###o###i###n###k### ###o###i###n###k### ###o###i###n###k###
oinky oinky oink
moo moo moo
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.ReplaceAll("oink oink oink", "oink", "moo")) // moo moo moo
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
fmt.Printf("%q\n", strings.Split("", ""))
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
}
输出:
["a" "b" "c"]
["" "man " "plan " "canal panama"]
[" " "x" "y" "z" " "]
[]
[""]
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Printf("%q\n", strings.SplitAfter("a,b,c", ",")) // ["a," "b," "c"]
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Printf("%q\n", strings.SplitAfterN("a,b,c", ",", 2)) // ["a," "b,c"]
fmt.Printf("%q\n", strings.SplitAfterN("a,b,c", ",", 0)) // []
fmt.Printf("%q\n", strings.SplitAfterN("a,b,c", ",", 5)) // ["a," "b," "c"]
fmt.Printf("%q\n", strings.SplitAfterN("a,b,c", ",", -1)) // ["a," "b," "c"]
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Printf("%q\n", strings.SplitN("a,b,c", ",", 2)) // ["a" "b,c"]
z := strings.SplitN("a,b,c", ",", 0)
fmt.Printf("%q (nil = %v)\n", z, z == nil) // [] (nil = true)
}
了解标准库是一个非常好的学习语言的方法。接下里的一些章节我们将详细研究标准库中的类型,方法,结合实际应用案例进行有趣的编程。
字符串处理是比较基础的库,我们从简单的库开始入门。
本节我们将继续用实例代码研究以下函数的用法。
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Index("chicken", "ken")) // 4
fmt.Println(strings.Index("chicken", "dmr")) // -1
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.IndexAny("chicken", "aeiouy")) // 2
fmt.Println(strings.IndexAny("crwth", "aeiouy")) // -1
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.IndexByte("golang", 'g')) // 0
fmt.Println(strings.IndexByte("gophers", 'h')) // 3
fmt.Println(strings.IndexByte("golang", 'x')) // -1
}
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
f := func(c rune) bool {
return unicode.Is(unicode.Han, c)
}
fmt.Println(strings.IndexFunc("Hello, 世界", f)) // 7
fmt.Println(strings.IndexFunc("Hello, world", f)) // -1
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.IndexRune("chicken", 'k')) // 4
fmt.Println(strings.IndexRune("chicken", 'd')) // -1
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.Index("go gopher", "go")) // 0
fmt.Println(strings.LastIndex("go gopher", "go")) // 3
fmt.Println(strings.LastIndex("go gopher", "rodent")) // -1
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.LastIndexAny("go gopher", "go")) // 4
fmt.Println(strings.LastIndexAny("go gopher", "rodent")) // 8
fmt.Println(strings.LastIndexAny("go gopher", "fail")) // -1
}
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println(strings.LastIndexByte("Hello, world", 'l')) // 10
fmt.Println(strings.LastIndexByte("Hello, world", 'o')) // 8
fmt.Println(strings.LastIndexByte("Hello, world", 'x')) // -1
}
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
fmt.Println(strings.LastIndexFunc("go 123", unicode.IsNumber)) // 5
fmt.Println(strings.LastIndexFunc("123 go", unicode.IsNumber)) // 2
fmt.Println(strings.LastIndexFunc("go", unicode.IsNumber)) // -1
}
了解标准库是一个非常好的学习语言的方法。接下里的一些章节我们将详细研究标准库中的类型,方法,结合实际应用案例进行有趣的编程。
字符串处理是比较基础的库,我们从简单的库开始入门。
本节我们将继续用实例代码研究以下函数的用法。
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
m := "hello this is strings."
// cut用sep参数分割原字符串,返回3个参数,切割后左边部分,切割后右边部分,是否存在sep,不存在就不切割
show := func(s, sep string) {
before, after, found := strings.Cut(s, sep)
fmt.Printf("Cut(%q, %q) = %q, %q, %v\n", s, sep, before, after, found)
}
show(m, "this")
show(m, "is")
show(m, "wow")
// cutprefix用于去掉前缀,如果原字符串以sep开头就去掉这一部分,返回剩余部分,以及是否存在布尔值
show2 := func(s, sep string) {
after, found := strings.CutPrefix(s, sep)
fmt.Printf("CutPrefix(%q, %q) = %q, %v\n", s, sep, after, found)
}
show2(m, "hello")
show2(m, "this")
show2(m, "wow")
// cutsuffix用于去掉后缀,如果原字符串以sep结尾就去掉这一部分,返回剩余部分,以及是否存在布尔值
show3 := func(s, sep string) {
after, found := strings.CutSuffix(s, sep)
fmt.Printf("CutSuffix(%q, %q) = %q, %v\n", s, sep, after, found)
}
show3(m, "strings")
show3(m, "strings.")
show3(m, "wow")
// equalFold是否在utf8编码下,不区分大小写相等
fmt.Println("Go equalFold go?", strings.EqualFold("Go", "go"))
// fields用空白字符分割字符串,包括空格,多个空格,\t,\r,\n
fmt.Println("Fields are 1: ", strings.Fields(" foo bar baz "))
fmt.Println("Fields are 2: ", strings.Fields(" foo bar\t baz "))
fmt.Println("Fields are 3: ", strings.Fields(" foo bar\r\nbaz "))
// fieldsFunc根据提供的函数分割字符串,下面的f函数判断当c不是字母也不是数字的时候返回true,也就是在此处分割
f := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
fmt.Println("Fields are 4: ", strings.FieldsFunc(" foo1;bar2,baz3...", f))
// hasPrefix, 区分大小写
fmt.Println(strings.HasPrefix(m, "Hello"))
// hasSuffix, 区分大小写
fmt.Println(strings.HasSuffix(m, "strings."))
}
输出:
Cut("hello this is strings.", "this") = "hello ", " is strings.", true
Cut("hello this is strings.", "is") = "hello th", " is strings.", true
Cut("hello this is strings.", "wow") = "hello this is strings.", "", false
CutPrefix("hello this is strings.", "hello") = " this is strings.", true
CutPrefix("hello this is strings.", "this") = "hello this is strings.", false
CutPrefix("hello this is strings.", "wow") = "hello this is strings.", false
CutSuffix("hello this is strings.", "strings") = "hello this is strings.", false
CutSuffix("hello this is strings.", "strings.") = "hello this is ", true
CutSuffix("hello this is strings.", "wow") = "hello this is strings.", false
Go equalFold go? true
Fields are 1: [foo bar baz]
Fields are 2: [foo bar baz]
Fields are 3: [foo bar baz]
Fields are 4: [foo1 bar2 baz3]
false
false
了解标准库是一个非常好的学习语言的方法。接下里的一些列章节我们将详细研究标准库中的类型,方法,结合实际应用案例进行有趣的编程。
字符串处理是比较基础的库,我们从简单的库开始入门。
先看一下strings相关的类型和方法,大概分为通用方法部分和Builder,Reader,Replacer类型。
package main
import (
"fmt"
"strings"
"unsafe"
)
func main() {
m := "hello this is strings."
c := strings.Clone(m)
// 判断相同指针,clone会创建新副本,因此这里返回false
fmt.Println(unsafe.StringData(c) == unsafe.StringData(m))
// 比较,返回0则相等
fmt.Println(strings.Compare(c, m))
// 是否包含hello字符串
fmt.Println(strings.Contains(c, "hello"))
// 是否含有xyzh中的任意一个字母
fmt.Println(strings.ContainsAny(c, "xyzh"))
// 是否有z字母
fmt.Println(strings.ContainsFunc(c, func(a rune) bool {
return a == 'z'
}))
// 是否有a字母
fmt.Println(strings.ContainsRune(c, 97))
// 字母i出现几次
fmt.Println(strings.Count(c, "i"))
}
false
0
true
true
false
false
3
employee, err := getInformation(1000)
if err != nil {
// Something is wrong. Do something.
}
package main
import (
"fmt"
"os"
)
type Employee struct {
ID int
FirstName string
LastName string
Address string
}
func main() {
employee, err := getInformation(1001)
if err != nil {
// Something is wrong. Do something.
} else {
fmt.Print(employee)
}
}
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
return employee, err
}
func apiCallEmployee(id int) (*Employee, error) {
employee := Employee{LastName: "Doe", FirstName: "John"}
return &employee, nil
}
错误处理策略
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
if err != nil {
return nil, err // Simply return the error to the caller.
}
return employee, nil
}
你可能还需要在传播错误之前添加更多信息。 为此,可以使用fmt.Errorf() 函数,该函数与我们之前看到的函数类似,但它返回一个错误。 例如,你可以向错误添加更多上下文,但仍返回原始错误,如下所示:
func getInformation(id int) (*Employee, error) {
employee, err := apiCallEmployee(1000)
if err != nil {
return nil, fmt.Errorf("Got an error when getting the employee information: %v", err)
}
return employee, nil
}
另一种策略是在错误为暂时性错误时运行重试逻辑。 例如,可以使用重试策略调用函数三次并等待两秒钟,如下所示:
func getInformation(id int) (*Employee, error) {
for tries := 0; tries < 3; tries++ {
employee, err := apiCallEmployee(1000)
if err == nil {
return employee, nil
}
fmt.Println("Server is not responding, retrying ...")
time.Sleep(time.Second * 2)
}
return nil, fmt.Errorf("server has failed to respond to get the employee information")
}
创建可重用的错误
var ErrNotFound = errors.New("Employee not found!")
func getInformation(id int) (*Employee, error) {
if id != 1001 {
return nil, ErrNotFound
}
employee := Employee{LastName: "Doe", FirstName: "John"}
return &employee, nil
}
employee, err := getInformation(1000)
if errors.Is(err, ErrNotFound) {
fmt.Printf("NOT FOUND: %v\n", err)
} else {
fmt.Print(employee)
}
用于错误处理的推荐做法
- 始终检查是否存在错误,即使预期不存在。 然后正确处理它们,以免向最终用户公开不必要的信息。
- 在错误消息中包含一个前缀,以便了解错误的来源。 例如,可以包含包和函数的名称。
- 创建尽可能多的可重用错误变量。
- 了解使用返回错误和panic之间的差异。 不能执行其他操作时再使用panic。 例如,如果某个依赖项未准备就绪,则程序运行无意义(除非你想要运行默认行为)。
- 在记录错误时记录尽可能多的详细信息(我们将在下一部分介绍记录方法),并打印出最终用户能够理解的错误。
实现转账方法
- 你需要实现向其他帐户转账的功能。 在本例中,你必须使用至少两个帐户来初始化程序,而不是像之前一样只使用一个帐户。
- 由于你要在核心程序包中添加新方法,因此请首先创建测试用例,以确保你编写正确的逻辑来进行转账。 请密切注意在函数与指针之间进行通信的方式。
- 你的转账方法应当接收你要转账的金额以及你将在其中增加资金的帐户对象。 请确保重用存款和取款方法以避免重复(特别是对于错误处理)。
- 请记住,如果你没有足够的资金,则无法向其他帐户转账。
修改对账单终结点以返回JSON对象
"{\"Name\":\"John\",\"Address\":\"Los Angeles, California\",\"Phone\":\"(213) 555 0147\",\"Number\":1001,\"Balance\":0}"
//...
func TestTransfer(t *testing.T) {
accountA := Account{
Customer: Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
Balance: 0,
}
accountB := Account{
Customer: Customer{
Name: "Mark",
Address: "Irvine, California",
Phone: "(949) 555 0198",
},
Number: 1002,
Balance: 0,
}
accountA.Deposit(100)
err := accountA.Transfer(50, &accountB)
if accountA.Balance != 50 && accountB.Balance != 50 {
t.Error("transfer from account A to account B is not working", err)
}
}
银行核心:bank.go
package bank
import (
"errors"
"fmt"
)
//...
// Transfer function
func (a *Account) Transfer(amount float64, dest *Account) error {
if amount <= 0 {
return errors.New("the amount to transfer should be greater than zero")
}
if a.Balance < amount {
return errors.New("the amount to transfer should be greater than the account's balance")
}
a.Withdraw(amount)
dest.Deposit(amount)
return nil
}
// Bank ...
type Bank interface {
Statement() string
}
// Statement ...
func Statement(b Bank) string {
return b.Statement()
}
银行API:main.go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/msft/bank"
)
var accounts = map[float64]*CustomAccount{}
func main() {
accounts[1001] = &CustomAccount{
Account: &bank.Account{
Customer: bank.Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
},
}
accounts[1002] = &CustomAccount{
Account: &bank.Account{
Customer: bank.Customer{
Name: "Mark",
Address: "Irvine, California",
Phone: "(949) 555 0198",
},
Number: 1002,
},
}
http.HandleFunc("/statement", statement)
http.HandleFunc("/deposit", deposit)
http.HandleFunc("/withdraw", withdraw)
http.HandleFunc("/transfer", transfer)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
//...
func transfer(w http.ResponseWriter, req *http.Request) {
numberqs := req.URL.Query().Get("number")
amountqs := req.URL.Query().Get("amount")
destqs := req.URL.Query().Get("dest")
if numberqs == "" {
fmt.Fprintf(w, "Account number is missing!")
return
}
if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
fmt.Fprintf(w, "Invalid account number!")
} else if amount, err := strconv.ParseFloat(amountqs, 64); err != nil {
fmt.Fprintf(w, "Invalid amount number!")
} else if dest, err := strconv.ParseFloat(destqs, 64); err != nil {
fmt.Fprintf(w, "Invalid account destination number!")
} else {
if accountA, ok := accounts[number]; !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", number)
} else if accountB, ok := accounts[dest]; !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", dest)
} else {
err := accountA.Transfer(amount, accountB.Account)
if err != nil {
fmt.Fprintf(w, "%v", err)
} else {
fmt.Fprintf(w, accountA.Statement())
}
}
}
}
func statement(w http.ResponseWriter, req *http.Request) {
numberqs := req.URL.Query().Get("number")
if numberqs == "" {
fmt.Fprintf(w, "Account number is missing!")
return
}
number, err := strconv.ParseFloat(numberqs, 64)
if err != nil {
fmt.Fprintf(w, "Invalid account number!")
} else {
account, ok := accounts[number]
if !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", number)
} else {
json.NewEncoder(w).Encode(bank.Statement(account))
}
}
}
// CustomAccount ...
type CustomAccount struct {
*bank.Account
}
// Statement ...
func (c *CustomAccount) Statement() string {
json, err := json.Marshal(c)
if err != nil {
return err.Error()
}
return string(json)
}
在内存中设置帐户
package main
import (
"github.com/msft/bank"
)
var accounts = map[float64]*bank.Account{}
func main() {
accounts[1001] = &bank.Account{
Customer: bank.Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
}
}
公开对账单方法
func statement(w http.ResponseWriter, req *http.Request) {
numberqs := req.URL.Query().Get("number")
if numberqs == "" {
fmt.Fprintf(w, "Account number is missing!")
return
}
if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
fmt.Fprintf(w, "Invalid account number!")
} else {
account, ok := accounts[number]
if !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", number)
} else {
fmt.Fprintf(w, account.Statement())
}
}
}
func main() {
accounts[1001] = &bank.Account{
Customer: bank.Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
}
http.HandleFunc("/statement", statement)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
如果在运行程序 (go run main.go) 时未看到任何错误或输出,则表明它在正常运行。 打开Web浏览器并输入URL http://localhost:8000/statement?number=1001,或在程序运行时在另一个shell中运行以下命令:
curl http://localhost:8000/statement?number=1001
应会看到以下输出:
1001 - John - 0
公开存款方法
func deposit(w http.ResponseWriter, req *http.Request) {
numberqs := req.URL.Query().Get("number")
amountqs := req.URL.Query().Get("amount")
if numberqs == "" {
fmt.Fprintf(w, "Account number is missing!")
return
}
if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
fmt.Fprintf(w, "Invalid account number!")
} else if amount, err := strconv.ParseFloat(amountqs, 64); err != nil {
fmt.Fprintf(w, "Invalid amount number!")
} else {
account, ok := accounts[number]
if !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", number)
} else {
err := account.Deposit(amount)
if err != nil {
fmt.Fprintf(w, "%v", err)
} else {
fmt.Fprintf(w, account.Statement())
}
}
}
}
}
http.HandleFunc("/statement", statement)
http.HandleFunc("/deposit", deposit)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
如果在运行程序 (go run main.go) 时未看到任何错误或输出,则表明它在正常运行。 打开Web浏览器并输入URL http://localhost:8000/deposit?number=1001&amount=100,或在程序运行时在另一个shell中运行以下命令:
curl "http://localhost:8000/deposit?number=1001&amount=100"
应会看到以下输出:
1001 - John - 100
公开取款方法
func withdraw(w http.ResponseWriter, req *http.Request) {
numberqs := req.URL.Query().Get("number")
amountqs := req.URL.Query().Get("amount")
if numberqs == "" {
fmt.Fprintf(w, "Account number is missing!")
return
}
if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
fmt.Fprintf(w, "Invalid account number!")
} else if amount, err := strconv.ParseFloat(amountqs, 64); err != nil {
fmt.Fprintf(w, "Invalid amount number!")
} else {
account, ok := accounts[number]
if !ok {
fmt.Fprintf(w, "Account with number %v can't be found!", number)
} else {
err := account.Withdraw(amount)
if err != nil {
fmt.Fprintf(w, "%v", err)
} else {
fmt.Fprintf(w, account.Statement())
}
}
}
}
现在,在main() 函数中添加 /withdraw终结点以公开你在withdraw() 函数中实现的逻辑。 将main() 函数修改为如下所示的形式:
func main() {
accounts[1001] = &bank.Account{
Customer: bank.Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
}
http.HandleFunc("/statement", statement)
http.HandleFunc("/deposit", deposit)
http.HandleFunc("/withdraw", withdraw)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
如果在运行程序 (go run main.go) 时未看到任何错误或输出,则表明它在正常运行。 打开Web浏览器并输入URL http://localhost:8000/withdraw?number=1001&amount=100,或在程序运行时在另一个shell中运行以下命令:
curl "http://localhost:8000/withdraw?number=1001&amount=100"
应会看到以下输出:
the amount to withdraw should be greater than the account's balance
请注意,我们收到的错误来自核心程序包。 当程序启动时,帐户余额为零。 因此,你无法提取任何金额的存款。 多次调用 /deposit终结点来添加资金,并再次调用 /withdraw终结点来确认它正常运行:
curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/withdraw?number=1001&amount=100"
应会看到以下输出:
1001 - John - 200
就这么简单! 你已创建了一个Web API,用于公开你从头构建的一个程序包中的功能。 请转到下一部分继续进行练习。 这次,你将编写自己的解决方案来完成一项挑战。
为客户和帐户创建结构
package bank
// Customer ...
type Customer struct {
Name string
Address string
Phone string
}
// Account ...
type Account struct {
Customer
Number int32
Balance float64
}
现在,在终端中运行go test -v命令时,你应该会看到测试通过:
=== RUN TestAccount
--- PASS: TestAccount (0.00s)
PASS
ok github.com/msft/bank 0.094s
实现存款方法
func TestDeposit(t *testing.T) {
account := Account{
Customer: Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
Balance: 0,
}
account.Deposit(10)
if account.Balance != 10 {
t.Error("balance is not being updated after a deposit")
}
}
运行go test -v时,应该会在输出中看到一个将失败的测试:
# github.com/msft/bank [github.com/msft/bank.test]
./bank_test.go:32:9: account.Deposit undefined (type Account has no field or method Deposit)
FAIL github.com/msft/bank [build failed]
// Deposit ...
func (a *Account) Deposit(amount float64) error {
if amount <= 0 {
return errors.New("the amount to deposit should be greater than zero")
}
a.Balance += amount
return nil
}
运行go test -v时,应该会看到测试通过:
=== RUN TestAccount
--- PASS: TestAccount (0.00s)
=== RUN TestDeposit
--- PASS: TestDeposit (0.00s)
PASS
ok github.com/msft/bank 0.193s
你还可以编写一个测试,
确认当尝试存入的金额为负时会出现错误,如下所示:
func TestDepositInvalid(t *testing.T) {
account := Account{
Customer: Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
Balance: 0,
}
if err := account.Deposit(-10); err == nil {
t.Error("only positive numbers should be allowed to deposit")
}
}
运行go test -v命令时,应该会看到测试通过:
=== RUN TestAccount
--- PASS: TestAccount (0.00s)
=== RUN TestDeposit
--- PASS: TestDeposit (0.00s)
=== RUN TestDepositInvalid
--- PASS: TestDepositInvalid (0.00s)
PASS
ok github.com/msft/bank 0.197s
实现取款方法
func TestWithdraw(t *testing.T) {
account := Account{
Customer: Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
Balance: 0,
}
account.Deposit(10)
account.Withdraw(10)
if account.Balance != 0 {
t.Error("balance is not being updated after withdraw")
}
}
运行go test -v命令时,应该会在输出中看到一个将失败的测试:
# github.com/msft/bank [github.com/msft/bank.test]
./bank_test.go:67:9: account.Withdraw undefined (type Account has no field or method Withdraw)
FAIL github.com/msft/bank [build failed]
// Withdraw ...
func (a *Account) Withdraw(amount float64) error {
if amount <= 0 {
return errors.New("the amount to withdraw should be greater than zero")
}
if a.Balance < amount {
return errors.New("the amount to withdraw should be greater than the account's balance")
}
a.Balance -= amount
return nil
}
运行go test -v命令时,应该会看到测试通过:
=== RUN TestAccount
--- PASS: TestAccount (0.00s)
=== RUN TestDeposit
--- PASS: TestDeposit (0.00s)
=== RUN TestDepositInvalid
--- PASS: TestDepositInvalid (0.00s)
=== RUN TestWithdraw
--- PASS: TestWithdraw (0.00s)
PASS
ok github.com/msft/bank 0.250s
实现对账单方法
func TestStatement(t *testing.T) {
account := Account{
Customer: Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
Balance: 0,
}
account.Deposit(100)
statement := account.Statement()
if statement != "1001 - John - 100" {
t.Error("statement doesn't have the proper format")
}
}
运行go test -v时,应该会在输出中看到一个将失败的测试:
# github.com/msft/bank [github.com/msft/bank.test]
./bank_test.go:86:22: account.Statement undefined (type Account has no field or method Statement)
FAIL github.com/msft/bank [build failed]
让我们编写Statement方法,该方法应返回一个字符串。 (你稍后必须覆盖此方法,这是一项挑战。)使用以下代码:
// Statement ...
func (a *Account) Statement() string {
return fmt.Sprintf("%v - %v - %v", a.Number, a.Name, a.Balance)
}
运行go test -v时,应该会看到测试通过:
=== RUN TestAccount
--- PASS: TestAccount (0.00s)
=== RUN TestDeposit
--- PASS: TestDeposit (0.00s)
=== RUN TestDepositInvalid
--- PASS: TestDepositInvalid (0.00s)
=== RUN TestWithdraw
--- PASS: TestWithdraw (0.00s)
=== RUN TestStatement
--- PASS: TestStatement (0.00s)
PASS
ok github.com/msft/bank 0.328s
接下来,请转到下一部分,编写Web API来公开Statement方法。
创建测试文件
package bank
import "testing"
func TestAccount(t *testing.T) {
}
打开一个终端,确保你处于 $GOPATH/src/bankcore/ 位置。 然后,使用以下命令在详细模式下运行测试:
go test -v
Go将查找所有 *_test.go文件来运行测试,因此你应该会看到以下输出:
=== RUN TestAccount
--- PASS: TestAccount (0.00s)
PASS
ok github.com/msft/bank 0.391s
编写将失败的测试
package bank
import "testing"
func TestAccount(t *testing.T) {
account := Account{
Customer: Customer{
Name: "John",
Address: "Los Angeles, California",
Phone: "(213) 555 0147",
},
Number: 1001,
Balance: 0,
}
if account.Name == "" {
t.Error("can't create an Account object")
}
}
# github.com/msft/bank [github.com/msft/bank.test]
.\bank_test.go:6:13: undefined: Account
.\bank_test.go:7:13: undefined: Customer
FAIL github.com/msft/bank [build failed]
让我们暂时先把它放在这里。 我们将完成此测试,并在为网上银行系统编写逻辑时创建新的测试。
定义功能和要求
创建初始项目文件
$GOPATH/
src/
bankcore/
go.mod
bank.go
bankapi/
go.mod
main.go
package bank
func Hello() string {
return "Hey! I'm working!"
}
我们将使用Go模块。 在src/bankcore/go.mod中添加以下内容,为此程序包提供一个正确的名称,以便以后可以引用它:
module github.com/msft/bank
go 1.14
然后,在src/bankapi/main.go中添加以下代码来调用bankcore程序包:
package main
import (
"fmt"
"github.com/msft/bank"
)
func main() {
fmt.Println(bank.Hello())
}
在src/bankapi/go.mod中,我们需要在本地引用bankcore程序包文件,如下所示:
module bankapi
go 1.14
require (
github.com/msft/bank v0.0.1
)
replace github.com/msft/bank => ../bankcore
若要确保一切正常,请在 $GOPATH/src/bankapi/ 目录中打开终端并运行以下命令:
go run main.go
应会看到以下输出:
Hey! I'm working!
此输出确认你的项目文件已完全按预期正确设置。 接下来,我们将开始编写代码,以实现我们的网上银行系统的初始功能集。
利用并发方法更快地计算斐波纳契数
package main
import (
"fmt"
"math/rand"
"time"
)
func fib(number float64) float64 {
x, y := 1.0, 1.0
for i := 0; i < int(number); i++ {
x, y = y, x+y
}
r := rand.Intn(3)
time.Sleep(time.Duration(r) * time.Second)
return x
}
func main() {
start := time.Now()
for i := 1; i < 15; i++ {
n := fib(float64(i))
fmt.Printf("Fib(%v): %v\n", i, n)
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
1
1
2
3
5
8
13
quit
Done calculating Fibonacci!
Done! It took 12.043196415 seconds!
实现并发并使程序的运行速度更快的改进版本如下所示:
package main
import (
"fmt"
"math/rand"
"time"
)
func fib(number float64, ch chan string) {
x, y := 1.0, 1.0
for i := 0; i < int(number); i++ {
x, y = y, x+y
}
r := rand.Intn(3)
time.Sleep(time.Duration(r) * time.Second)
ch <- fmt.Sprintf("Fib(%v): %v\n", number, x)
}
func main() {
start := time.Now()
size := 15
ch := make(chan string, size)
for i := 0; i < size; i++ {
go fib(float64(i), ch)
}
for i := 0; i < size; i++ {
fmt.Printf(<-ch)
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
输出:
Fib(14): 610
Fib(8): 34
Fib(1): 1
Fib(5): 8
Fib(0): 1
Fib(12): 233
Fib(2): 2
Fib(13): 377
Fib(6): 13
Fib(7): 21
Fib(4): 5
Fib(3): 3
Fib(10): 89
Fib(9): 55
Fib(11): 144
Done! It took 2 seconds!
使用两个无缓冲channel的程序的第二个版本如下所示:
package main
import (
"fmt"
"time"
)
var quit = make(chan bool)
func fib(c chan int) {
x, y := 1, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("Done calculating Fibonacci!")
return
}
}
}
func main() {
start := time.Now()
command := ""
data := make(chan int)
go fib(data)
for {
num := <-data
fmt.Println(num)
fmt.Scanf("%s", &command)
if num > 10000 {
quit <- true
break
}
}
time.Sleep(1 * time.Second)
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
输出:
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
Done calculating Fibonacci!
Done! It took 1 seconds!
ch := make(chan string, 10)
package main
import (
"fmt"
)
func send(ch chan string, message string) {
ch <- message
}
func main() {
size := 4
ch := make(chan string, size)
send(ch, "one")
send(ch, "two")
send(ch, "three")
send(ch, "four")
fmt.Println("All data sent to the channel ...")
for i := 0; i < size; i++ {
fmt.Println(<-ch)
}
fmt.Println("Done!")
}
运行程序时,将看到以下输出:
All data sent to the channel ...
one
two
three
four
Done!
你可能会说我们在这里没有做任何不同的操作,你是对的。 但是让我们看看当你将size变量更改为一个更小的数字(你甚至可以尝试使用一个更大的数字)时会发生什么情况,如下所示:
size := 2
重新运行程序时,将看到以下错误:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.send(...)
/Users/developer/go/src/concurrency/main.go:8
main.main()
/Users/developer/go/src/concurrency/main.go:16 +0xf3
exit status 2
func main() {
size := 2
ch := make(chan string, size)
send(ch, "one")
send(ch, "two")
go send(ch, "three")
go send(ch, "four")
fmt.Println("All data sent to the channel ...")
for i := 0; i < 4; i++ {
fmt.Println(<-ch)
}
fmt.Println("Done!")
}
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
start := time.Now()
apis := []string{
"https://management.azure.com",
"https://dev.azure.com",
"https://api.github.com",
"https://outlook.office.com/",
"https://api.somewhereintheinternet.com/",
"https://graph.microsoft.com",
}
ch := make(chan string, 10)
for _, api := range apis {
go checkAPI(api, ch)
}
for i := 0; i < len(apis); i++ {
fmt.Print(<-ch)
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
func checkAPI(api string, ch chan string) {
_, err := http.Get(api)
if err != nil {
ch <- fmt.Sprintf("ERROR: %s is down!\n", api)
return
}
ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
无缓冲channel与有缓冲channel
Channel方向
chan<- int // 仅用于写入数据的chan
<-chan int // 仅用于读取数据的chan
package main
import "fmt"
func send(ch chan<- string, message string) {
fmt.Printf("Sending: %#v\n", message)
ch <- message
}
func read(ch <-chan string) {
fmt.Printf("Receiving: %#v\n", <-ch)
}
func main() {
ch := make(chan string, 1)
send(ch, "Hello World!")
read(ch)
}
运行程序时,将看到以下输出:
Sending: "Hello World!"
Receiving: "Hello World!"
程序阐明每个函数中每个channel的意图。 如果试图使用一个channel在一个仅用于接收数据的channel中发送数据,将会出现编译错误。 例如,尝试执行如下所示的操作:
func read(ch <-chan string) {
fmt.Printf("Receiving: %#v\n", <-ch)
ch <- "Bye!"
}
运行程序时,将看到以下错误:
# command-line-arguments
./main.go:12:5: invalid operation: ch <- "Bye!" (send to receive-only type <-chan string)
编译错误总比误用channel好。
多路复用
package main
import (
"fmt"
"time"
)
func process(ch chan string) {
time.Sleep(3 * time.Second)
ch <- "Done processing!"
}
func replicate(ch chan string) {
time.Sleep(1 * time.Second)
ch <- "Done replicating!"
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go process(ch1)
go replicate(ch2)
for i := 0; i < 2; i++ {
select {
case process := <-ch1:
fmt.Println(process)
case replicate := <-ch2:
fmt.Println(replicate)
}
}
}
运行程序时,将看到以下输出:
Done replicating!
Done processing!
请注意,replicate函数首先完成,这就是首先在终端中看到其输出的原因。 main函数存在一个循环,因为select语句在收到事件后立即结束,但我们仍在等待process函数完成。
Channel语法
ch := make(chan int)
ch <- x // 向ch写入数据x = <-ch // 从ch读取数据
<-ch // 从ch读取数据但不做任何处理
可在channel中执行的另一项操作是关闭channel。 若要关闭通道,使用内置的close() 函数:
close(ch)
ch := make(chan string)
func checkAPI(api string, ch chan string) {
_, err := http.Get(api)
if err != nil {
ch <- fmt.Sprintf("ERROR: %s is down!\n", api)
return
}
ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
fmt.Print(<-ch)
ERROR: https://api.somewhereintheinternet.com/ is down!
Done! It took 0.007401217 seconds!
无缓冲channel
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
fmt.Print(<-ch)
fmt.Print(<-ch)
重新运行程序时,会看到如下所示的输出:
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
Done! It took 0.263611711 seconds!
请注意,现在你会看到两个API的输出。 如果继续添加更多fmt.Print(<-ch) 行,你最终将会读取发送到channel的所有数据。 但是如果你试图读取更多数据,而没有goroutine再发送数据,会发生什么呢? 例如:
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
fmt.Print(<-ch)
重新运行程序时,会看到如下所示的输出:
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
SUCCESS: https://dev.azure.com is up and running!
for i := 0; i < len(apis); i++ {
fmt.Print(<-ch)
}
以下是程序的最终版本,以防你的版本出错:
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
start := time.Now()
apis := []string{
"https://management.azure.com",
"https://dev.azure.com",
"https://api.github.com",
"https://outlook.office.com/",
"https://api.somewhereintheinternet.com/",
"https://graph.microsoft.com",
}
ch := make(chan string)
for _, api := range apis {
go checkAPI(api, ch)
}
for i := 0; i < len(apis); i++ {
fmt.Print(<-ch)
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
func checkAPI(api string, ch chan string) {
_, err := http.Get(api)
if err != nil {
ch <- fmt.Sprintf("ERROR: %s is down!\n", api)
return
}
ch <- fmt.Sprintf("SUCCESS: %s is up and running!\n", api)
}
重新运行程序时,会看到如下所示的输出:
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://dev.azure.com is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
Done! It took 0.602099714 seconds!
Go实现并发的方法
Goroutine
func main(){
login()
go launch()
}
你还会发现,许多程序喜欢使用匿名函数来创建goroutine,如此代码中所示:
func main(){
login()
go func() {
launch()
}()
}
为了查看运行中的goroutine,让我们编写一个并发程序。
编写并发程序
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
start := time.Now()
apis := []string{
"https://management.azure.com",
"https://dev.azure.com",
"https://api.github.com",
"https://outlook.office.com/",
"https://api.somewhereintheinternet.com/",
"https://graph.microsoft.com",
}
for _, api := range apis {
_, err := http.Get(api)
if err != nil {
fmt.Printf("ERROR: %s is down!\n", api)
continue
}
fmt.Printf("SUCCESS: %s is up and running!\n", api)
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
运行前面的代码时,将看到以下输出:
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://dev.azure.com is up and running!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://graph.microsoft.com is up and running!
Done! It took 1.658436834 seconds!
func checkAPI(api string) {
_, err := http.Get(api)
if err != nil {
fmt.Printf("ERROR: %s is down!\n", api)
return
}
fmt.Printf("SUCCESS: %s is up and running!\n", api)
}
注意,我们不再需要continue关键字,因为我们不在for循环中。 要停止函数的执行流,只需使用return关键字。 现在,我们需要修改main() 函数中的代码,为每个API创建一个goroutine,如下所示:
for _, api := range apis {
go checkAPI(api)
}
Done! It took 1.506e-05 seconds!
for _, api := range apis {
go checkAPI(api)
}
time.Sleep(3 * time.Second)
ERROR: https://api.somewhereintheinternet.com/ is down!
SUCCESS: https://api.github.com is up and running!
SUCCESS: https://management.azure.com is up and running!
SUCCESS: https://dev.azure.com is up and running!
SUCCESS: https://outlook.office.com/ is up and running!
SUCCESS: https://graph.microsoft.com is up and running!
Done! It took 3.002114573 seconds!
实现代码:
package main
import "fmt"
func compressLZW(testStr string) []int {
code := 256
dictionary := make(map[string]int)
for i := 0; i < 256; i++ {
dictionary[string(i)] = i
}
currChar := ""
result := make([]int, 0)
for _, c := range []byte(testStr) {
phrase := currChar + string(c)
if _, isTrue := dictionary[phrase]; isTrue {
currChar = phrase
} else {
result = append(result, dictionary[currChar])
dictionary[phrase] = code
code++
currChar = string(c)
}
}
if currChar != "" {
result = append(result, dictionary[currChar])
}
return result
}
func decompressLZW(compressed []int) string {
code := 256
dictionary := make(map[int]string)
for i := 0; i < 256; i++ {
dictionary[i] = string(i)
}
currChar := string(compressed[0])
result := currChar
for _, element := range compressed[1:] {
var word string
if x, ok := dictionary[element]; ok {
word = x
} else if element == code {
word = currChar + currChar[:1]
} else {
panic(fmt.Sprintf("Bad compressed element: %d", element))
}
result += word
dictionary[code] = currChar + word[:1]
code++
currChar = word
}
return result
}
func main() {
fmt.Print("Enter any string :")
var testStr string
fmt.Scanln(&testStr)
compressed := compressLZW(testStr)
fmt.Println("\nAfter Compression :", compressed)
uncompression := decompressLZW(compressed)
fmt.Println("\nAfter Uncompression :", uncompression)
}
输出:
Enter any string :Australia
After Compression : [65 117 115 116 114 97 108 105 97]
After Uncompression : Australia
C:\golang\example>go run test.go
Enter any string :Germany
After Compression : [71 101 114 109 97 110 121]
After Uncompression : Germany
排列是将有序列表S的元素重新排列为与S本身一一对应的对应关系。长度为n的字符串有n!排列。让我们以"ABCD"为例,编写一个程序来生成Golang中所有可能的字符串排列和组合。
实现代码:
package main
import (
"fmt"
)
func join(ins []rune, c rune) (res []string) {
for i := 0; i <= len(ins); i++ {
res = append(res, string(ins[:i])+string(c)+string(ins[i:]))
}
return res
}
func permutations(testStr string) []string {
var n func(testStr []rune, p []string) []string
n = func(testStr []rune, p []string) []string {
if len(testStr) == 0 {
return p
} else {
result := []string{}
for _, e := range p {
result = append(result, join([]rune(e), testStr[0])...)
}
return n(testStr[1:], result)
}
}
output := []rune(testStr)
return n(output[1:], []string{string(output[0])})
}
func main() {
d := permutations("ABCD")
fmt.Println(d)
}
输出:
[DCBA CDBA CBDA CBAD DBCA BDCA BCDA BCAD DBAC BDAC BADC BACD DCAB CDAB CADB CABD DACB ADCB ACDB ACBD DABC ADBC ABDC ABCD]
AVL树是高度平衡的二叉搜索树。AVL树检查左侧和右侧子树的高度,并确保差异不超过1,这种差异称为平衡因子。
实现代码:
package main
import (
"encoding/json"
"fmt"
)
type Key interface {
Less(Key) bool
Eq(Key) bool
}
type Node struct {
Data Key
Balance int
Link [2]*Node
}
func opp(dir int) int {
return 1 - dir
}
// single rotation
func single(root *Node, dir int) *Node {
save := root.Link[opp(dir)]
root.Link[opp(dir)] = save.Link[dir]
save.Link[dir] = root
return save
}
// double rotation
func double(root *Node, dir int) *Node {
save := root.Link[opp(dir)].Link[dir]
root.Link[opp(dir)].Link[dir] = save.Link[opp(dir)]
save.Link[opp(dir)] = root.Link[opp(dir)]
root.Link[opp(dir)] = save
save = root.Link[opp(dir)]
root.Link[opp(dir)] = save.Link[dir]
save.Link[dir] = root
return save
}
// adjust valance factors after double rotation
func adjustBalance(root *Node, dir, bal int) {
n := root.Link[dir]
nn := n.Link[opp(dir)]
switch nn.Balance {
case 0:
root.Balance = 0
n.Balance = 0
case bal:
root.Balance = -bal
n.Balance = 0
default:
root.Balance = 0
n.Balance = bal
}
nn.Balance = 0
}
func insertBalance(root *Node, dir int) *Node {
n := root.Link[dir]
bal := 2*dir - 1
if n.Balance == bal {
root.Balance = 0
n.Balance = 0
return single(root, opp(dir))
}
adjustBalance(root, dir, bal)
return double(root, opp(dir))
}
func insertR(root *Node, data Key) (*Node, bool) {
if root == nil {
return &Node{Data: data}, false
}
dir := 0
if root.Data.Less(data) {
dir = 1
}
var done bool
root.Link[dir], done = insertR(root.Link[dir], data)
if done {
return root, true
}
root.Balance += 2*dir - 1
switch root.Balance {
case 0:
return root, true
case 1, -1:
return root, false
}
return insertBalance(root, dir), true
}
// Insert a node into the AVL tree.
func Insert(tree **Node, data Key) {
*tree, _ = insertR(*tree, data)
}
// Remove a single item from an AVL tree.
func Remove(tree **Node, data Key) {
*tree, _ = removeR(*tree, data)
}
func removeBalance(root *Node, dir int) (*Node, bool) {
n := root.Link[opp(dir)]
bal := 2*dir - 1
switch n.Balance {
case -bal:
root.Balance = 0
n.Balance = 0
return single(root, dir), false
case bal:
adjustBalance(root, opp(dir), -bal)
return double(root, dir), false
}
root.Balance = -bal
n.Balance = bal
return single(root, dir), true
}
func removeR(root *Node, data Key) (*Node, bool) {
if root == nil {
return nil, false
}
if root.Data.Eq(data) {
switch {
case root.Link[0] == nil:
return root.Link[1], false
case root.Link[1] == nil:
return root.Link[0], false
}
heir := root.Link[0]
for heir.Link[1] != nil {
heir = heir.Link[1]
}
root.Data = heir.Data
data = heir.Data
}
dir := 0
if root.Data.Less(data) {
dir = 1
}
var done bool
root.Link[dir], done = removeR(root.Link[dir], data)
if done {
return root, true
}
root.Balance += 1 - 2*dir
switch root.Balance {
case 1, -1:
return root, true
case 0:
return root, false
}
return removeBalance(root, dir)
}
type intKey int
func (k intKey) Less(k2 Key) bool { return k < k2.(intKey) }
func (k intKey) Eq(k2 Key) bool { return k == k2.(intKey) }
func main() {
var tree *Node
fmt.Println("Empty Tree:")
avl,_ := json.MarshalIndent(tree, "", " ")
fmt.Println(string(avl))
fmt.Println("\nInsert Tree:")
Insert(&tree, intKey(4))
Insert(&tree, intKey(2))
Insert(&tree, intKey(7))
Insert(&tree, intKey(6))
Insert(&tree, intKey(6))
Insert(&tree, intKey(9))
avl,_ = json.MarshalIndent(tree, "", " ")
fmt.Println(string(avl))
fmt.Println("\nRemove Tree:")
Remove(&tree, intKey(4))
Remove(&tree, intKey(6))
avl,_ = json.MarshalIndent(tree, "", " ")
fmt.Println(string(avl))
}
输出:
Empty Tree:
null
Insert Tree:
{
"Data": 6,
"Balance": 0,
"Link": [
{
"Data": 4,
"Balance": 0,
"Link": [
{
"Data": 2,
"Balance": 0,
"Link": [
null,
null
]
},
{
"Data": 6,
"Balance": 0,
"Link": [
null,
null
]
}
]
},
{
"Data": 7,
"Balance": 1,
"Link": [
null,
{
"Data": 9,
"Balance": 0,
"Link": [
null,
null
]
}
]
}
]
}
Remove Tree:
{
"Data": 6,
"Balance": 1,
"Link": [
{
"Data": 2,
"Balance": 0,
"Link": [
null,
null
]
},
{
"Data": 7,
"Balance": 1,
"Link": [
null,
{
"Data": 9,
"Balance": 0,
"Link": [
null,
null
]
}
]
}
]
}
绘制2D矩阵,以螺旋格式打印给定矩阵的所有元素。给定一个数字n,使用O(1) 空间顺时针方向打印n x n螺旋矩阵(从1到n x n的数字)。
实现代码:
package main
import (
"fmt"
)
func spiral(n int) []int {
left, top, right, bottom := 0, 0, n-1, n-1
sz := n * n
s := make([]int, sz)
i := 0
for left < right {
// work right, along top
for c := left; c <= right; c++ {
s[top*n+c] = i
i++
}
top++
// work down right side
for r := top; r <= bottom; r++ {
s[r*n+right] = i
i++
}
right--
if top == bottom {
break
}
// work left, along bottom
for c := right; c >= left; c-- {
s[bottom*n+c] = i
i++
}
bottom--
// work up left side
for r := bottom; r >= top; r-- {
s[r*n+left] = i
i++
}
left++
}
// center (last) element
s[top*n+left] = i
return s
}
func main() {
num := 5
len := 2
for i, draw := range spiral(num) {
fmt.Printf("%*d ", len, draw)
if i%num == num-1 {
fmt.Println("")
}
}
}
输出:
0 1 2 3 4
15 16 17 18 5
14 23 24 19 6
13 22 21 20 7
12 11 10 9 8