溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

反轉(zhuǎn) UTF-8 編碼的字符串

發(fā)布時(shí)間:2020-06-19 21:42:12 來(lái)源:網(wǎng)絡(luò) 閱讀:646 作者:騎士救兵 欄目:編程語(yǔ)言

反轉(zhuǎn) UTF-8 編碼的字符串

實(shí)現(xiàn)一個(gè) reverse 函數(shù),反轉(zhuǎn) UTF-8 編碼的字符串中的字符元素。傳入的參數(shù)是字符串對(duì)應(yīng)的字節(jié)切片類型([]byte)。

簡(jiǎn)單的實(shí)現(xiàn)

首先,不考慮效率,先用一個(gè)簡(jiǎn)單的邏輯來(lái)實(shí)現(xiàn)。切片的反轉(zhuǎn)方法如下:

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

只要將數(shù)據(jù)轉(zhuǎn)成字符切片,然后套用切片反轉(zhuǎn)的代碼就可以了:

// 先轉(zhuǎn)成字符切片,然后再用切片的反轉(zhuǎn)的方法,最后更新參數(shù)指向的底層數(shù)組
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]
    }
}

這個(gè)方法邏輯清晰,適合之后拿來(lái)做隨機(jī)測(cè)試

原地反轉(zhuǎn)

下面的函數(shù)實(shí)現(xiàn)了原地反轉(zhuǎn)。每次讀取第一個(gè)字符,把尾部標(biāo)志位之前的一串字符移到開(kāi)頭,再把之前讀的字符放到標(biāo)志位的位置,然后向前移動(dòng)標(biāo)志位:

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

這個(gè)實(shí)現(xiàn)效率還是稍差,之后會(huì)做測(cè)試比較。

高效的原地反轉(zhuǎn)

下面的函數(shù),用了很多的標(biāo)志位,實(shí)現(xiàn)了一個(gè)高效的原地反轉(zhuǎn):

func reverse(s []byte) {
    var (
        lRd, rRd           int  // 讀指針
        lWr, rWr           int  // 寫(xiě)指針
        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
        }
    }

    // 最后還可能會(huì)剩個(gè)字符沒(méi)寫(xiě)
    if lHasRune {
        utf8.EncodeRune(s[rWr-lsize:], lr)
    }
    if rHasRune {
        utf8.EncodeRune(s[lWr:], rr)
    }
}

測(cè)試驗(yàn)證

下面是測(cè)試代碼,來(lái)驗(yàn)證上面的函數(shù)的正確性以及效率。

功能測(cè)試

基于表的測(cè)試很直觀也很簡(jiǎn)單,可以方便的添加更多測(cè)試用例:

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

測(cè)試結(jié)果:

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>

隨機(jī)測(cè)試

隨機(jī)測(cè)試也是功能測(cè)試的一種,通過(guò)構(gòu)建隨機(jī)輸入來(lái)擴(kuò)展測(cè)試的覆蓋范圍。有兩種策略:

  • 額外寫(xiě)一個(gè)函數(shù),這個(gè)函數(shù)使用低效但是清晰的算法,然后檢查兩種實(shí)現(xiàn)的輸出是否一致
  • 構(gòu)建符合某種模式的輸入,這樣就可以知道對(duì)應(yīng)的輸出。

下面就是用第一種策略寫(xiě)的隨機(jī)測(cè)試。為了讓輸出的內(nèi)容有更好的可讀性,選擇了一些熟悉的字符生成隨機(jī)字符:

// randomSte 返回一個(gè)隨機(jī)字符串,它的長(zhǎng)度和內(nèi)容都是隨機(jī)生成的
func randomStr(rng *rand.Rand) string {
    n := rng.Intn(25) // 隨機(jī)字符串最大長(zhǎng)度24
    runes := make([]rune, n)
    for i := 0; i < n; i++ {
        var r rune
        switch rune(rng.Intn(6)) {
        case 0: // ASCII 字母,1個(gè)字節(jié)
            r = rune(rng.Intn(0x4B) + 0x30)
        case 1: // 希臘字母,2個(gè)字節(jié)
            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))
        }
    }
}

測(cè)試結(jié)果:

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 參數(shù),查看詳細(xì)的測(cè)試日志。

基準(zhǔn)測(cè)試

基準(zhǔn)測(cè)試也沒(méi)什么特別的,把功能測(cè)試的測(cè)試用例全部跑一遍:

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

測(cè)試結(jié)果:

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>
向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI