實現一個 reverse 函數,反轉 UTF-8 編碼的字符串中的字符元素。傳入的參數是字符串對應的字節切片類型([]byte)。算法
首先,不考慮效率,先用一個簡單的邏輯來實現。切片的反轉方法以下:windows
func reverse(s []int) { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] } }
只要將數據轉成字符切片,而後套用切片反轉的代碼就能夠了:數組
// 先轉成字符切片,而後再用切片的反轉的方法,最後更新參數指向的底層數組 func reverse_rune(slice []byte) { r := []rune(string(slice)) for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] } for i := range slice { slice[i] = []byte(string(r))[i] } }
這個方法邏輯清晰,適合以後拿來作隨機測試dom
下面的函數實現了原地反轉。每次讀取第一個字符,把尾部標誌位以前的一串字符移到開頭,再把以前讀的字符放到標誌位的位置,而後向前移動標誌位:ide
func reverse_byte(slice []byte) { for l := len(slice); l > 0; { r, size := utf8.DecodeRuneInString(string(slice[0:])) copy(slice[0:l], slice[0+size:l]) copy(slice[l-size:l], []byte(string(r))) l -= size } }
這個實現效率仍是稍差,以後會作測試比較。 函數
下面的函數,用了不少的標誌位,實現了一個高效的原地反轉:測試
func reverse(s []byte) { var ( lRd, rRd int // 讀指針 lWr, rWr int // 寫指針 lHasRune, rHasRune bool // 是否有字符 lr, rr rune // 讀取到的字符 lsize, rsize int // 讀取到字符的寬度 ) rRd, rWr = len(s), len(s) for lRd < rRd { if !lHasRune { lr, lsize = utf8.DecodeRune(s[lRd:]) lRd += lsize lHasRune = true } if !rHasRune { rr, rsize = utf8.DecodeLastRune(s[:rRd]) rRd -= rsize rHasRune = true } if lsize <= rWr-rRd { utf8.EncodeRune(s[rWr-lsize:], lr) rWr -= lsize lHasRune = false } if rsize <= lRd-lWr { utf8.EncodeRune(s[lWr:], rr) lWr += rsize rHasRune = false } } // 最後還可能會剩個字符沒寫 if lHasRune { utf8.EncodeRune(s[rWr-lsize:], lr) } if rHasRune { utf8.EncodeRune(s[lWr:], rr) } }
下面是測試代碼,來驗證上面的函數的正確性以及效率。 編碼
基於表的測試很直觀也很簡單,能夠方便的添加更多測試用例:指針
var tests = []struct { input string want string }{ {"abc", "cba"}, {"123", "321"}, {"你好,世界!", "!界世,好你"}, {"a一二三,四五.六,z", "z,六.五四,三二一a"}, } func TestReverse_rune(t *testing.T) { for _, test := range tests { s := []byte(test.input) reverse_rune(s) if string(s) != test.want { t.Errorf("reverse(%q) = %q, want %q\n", test.input, string(s), test.want) } } } func TestReverse_byte(t *testing.T) { for _, test := range tests { s := []byte(test.input) reverse_byte(s) if string(s) != test.want { t.Errorf("reverse(%q) = %q, want %q\n", test.input, string(s), test.want) } } } func TestReverse(t *testing.T) { for _, test := range tests { s := []byte(test.input) reverse(s) if string(s) != test.want { t.Errorf("reverse(%q) = %q, want %q\n", test.input, string(s), test.want) } } }
測試結果:日誌
PS H:\Go\src\gopl\exercise4\e7> go test -run TestReverse -v === RUN TestReverse_rune --- PASS: TestReverse_rune (0.00s) === RUN TestReverse_byte --- PASS: TestReverse_byte (0.00s) === RUN TestReverse --- PASS: TestReverse (0.00s) PASS ok gopl/exercise4/e7 0.263s PS H:\Go\src\gopl\exercise4\e7>
隨機測試也是功能測試的一種,經過構建隨機輸入來擴展測試的覆蓋範圍。有兩種策略:
下面就是用第一種策略寫的隨機測試。爲了讓輸出的內容有更好的可讀性,選擇了一些熟悉的字符生成隨機字符:
// randomSte 返回一個隨機字符串,它的長度和內容都是隨機生成的 func randomStr(rng *rand.Rand) string { n := rng.Intn(25) // 隨機字符串最大長度24 runes := make([]rune, n) for i := 0; i < n; i++ { var r rune switch rune(rng.Intn(6)) { case 0: // ASCII 字母,1個字節 r = rune(rng.Intn(0x4B) + 0x30) case 1: // 希臘字母,2個字節 r = rune(rng.Intn(57) + 0x391) case 2: // 日文 r = rune(rng.Intn(0xBF) + 0x3041) case 3: // 韓文 r = rune(rng.Intn(0x2BA4) + 0xAC00) case 4, 5, 6: // 中文 r = rune(rng.Intn(0x4E00) + 0x51D6) } runes[i] = r } return string(runes) } func TestRandomReverse(t *testing.T) { seed := time.Now().UTC().UnixNano() t.Logf("Random seed: %d", seed) rng := rand.New(rand.NewSource(seed)) for i := 0; i < 1000; i++ { test := randomStr(rng) s1 := []byte(test) reverse_rune(s1) t.Logf("%s => %s\n", test, string(s1)) s2 := []byte(test) reverse_byte(s2) if string(s1) != string(s2) { t.Errorf("reverse_byte(%q) = %q, want %q\n", test, string(s2), string(s1)) } s3 := []byte(test) reverse(s3) if string(s1) != string(s3) { t.Errorf("reverse_byte(%q) = %q, want %q\n", test, string(s3), string(s1)) } } }
測試結果:
PS H:\Go\src\gopl\exercise4\e7> go test -run Random PASS ok gopl/exercise4/e7 0.298s PS H:\Go\src\gopl\exercise4\e7>
還能夠加上 -v 參數,查看詳細的測試日誌。
基準測試也沒什麼特別的,把功能測試的測試用例所有跑一遍:
func BenchmarkReverse(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range tests { reverse([]byte(test.input)) } } } func BenchmarkReverse_rune(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range tests { reverse_rune([]byte(test.input)) } } } func BenchmarkReverse_byte(b *testing.B) { for i := 0; i < b.N; i++ { for _, test := range tests { reverse_byte([]byte(test.input)) } } }
測試結果:
PS H:\Go\src\gopl\exercise4\e7> go test -benchmem -bench . goos: windows goarch: amd64 pkg: gopl/exercise4/e7 BenchmarkReverse-8 5000000 286 ns/op 0 B/op 0 allocs/op BenchmarkReverse_rune-8 500000 3610 ns/op 0 B/op 0 allocs/op BenchmarkReverse_byte-8 3000000 583 ns/op 0 B/op 0 allocs/op PASS ok gopl/exercise4/e7 6.226s PS H:\Go\src\gopl\exercise4\e7>