Go Rune

Go 字串處理使用 Rune

前言

當在 Go 字串中索引位置 n 時,為什麼沒有得到第 n 個字元?
func main() {
foo := "ABC"
for _, v := range foo {
fmt.Println(v) // 65, 66, 67
}
}

相較於其他程式語言一串文字在遍歷時會預期拿到單一個字符,在 Go 會拿到「Rune」;如果直接透過索引取得 string 內容會拿到 byte:

func main() {
s := "Hello世界"
fmt.Println(len(s)) // 11 byte
fmt.Println(s[0]) // 72 (H 的 byte 值)
fmt.Println(s[5]) // 228 (世 的第一個 byte)
}

啥是 Rune?

要創建一個 Rune 可以透過 '' 單引號定義:

r := 'A'

Rune 是內建的類型,實際上是 int32 的別名(所有方面都等價),設計用來表示一個 Unicode 碼點(code point),使得 Go 能夠正確處理各種語言的字符。

String vs Rune vs Byte

  • Byte(位元組)
    • uint8 的別名,代表一個 8 位元的值(0-255)
    • 構成字串的基本單位
  • String(字串)
    • 由一系列 byte 組成的不可變序列
    • 可以包含任何有效的 UTF-8 字符
  • Rune(符文)
    • int32 的別名
    • 設計用於存放 Unicode codepoint

實際案例

轉換為 Rune Slice

func main() {
s := "Hello世界"
runes := []rune(s)
fmt.Println(runes[5]) // 19990 (世)
fmt.Printf("%c\n", runes[5]) // 世
fmt.Println("字符數量:", len(runes)) // 7
}

使用 Range 遍歷

func main() {
s := "Hello世界"
for i, r := range s {
fmt.Printf("索引: %d, 字符: %c, Unicode: U+%04X\n", i, r, r)
}
// 索引: 0, 字符: H, Unicode: U+0048
// 索引: 1, 字符: e, Unicode: U+0065
// 索引: 2, 字符: l, Unicode: U+006C
// 索引: 3, 字符: l, Unicode: U+006C
// 索引: 4, 字符: o, Unicode: U+006F
// 索引: 5, 字符: 世, Unicode: U+4E16
// 索引: 8, 字符: 界, Unicode: U+754C
}

Indexed capitalization🔗

給定一個由「小寫字母」組成的字串和「一個整數索引」陣列,將指定索引處的所有字母大寫。如果索引超出字串範圍,則忽略該索引。

Terminal window
"abcdef", [1,2,5] ==> "aBCdeF"
"abcdef", [1,2,5,100] ==> "aBCdeF" // There is no index 100.

初步我的想法是創建空的 []rune 並遍歷字串 st 判斷當前字元是否存在 arr 當中,如果是則推入大寫反之小寫。所以會是 O(n × m)。

import "unicode"
func Capitalize(st string, arr []int) string {
result := []rune{}
for i, c := range st {
if contains(arr, i) {
result = append(result, unicode.ToUpper(c))
} else {
result = append(result, c)
}
}
return string(result)
}
func contains(arr []int, val int) bool {
for _, v := range arr {
if v == val {
return true
}
}
return false
}

額外留意:Byte Index 不是 Char Index

假設題目沒有輸入為英文小寫的限制。

綜合以上對 string 處理的知識,可以發現 i 實際上是 string 背後的 byte,而一個字符可能由多個 byte 構成導致出錯,應該以字元 index 為基準而非 byte index:

func CapitalizeByCharIndex(st string, arr []int) string {
result := []rune{}
charIndex := 0 // 手動維護字符索引
for _, c := range st { // 不用 byte 索引 i
if contains(arr, charIndex) {
result = append(result, unicode.ToUpper(c))
} else {
result = append(result, c)
}
charIndex++ // 每個字符遞增
}
return string(result)
}

換個思路:從要轉大寫的陣列下手

st 轉換為 []rune 背後實際上是一次遍歷轉換,再透過遍歷 arr 覆寫上大寫的字元 ,整個流程更直白是 O(n + m)。

func CapitalizeByChar(st string, arr []int) string {
runes := []rune(st)
for _, idx := range arr {
if idx < len(runes) {
runes[idx] = unicode.ToUpper(runes[idx])
}
}
return string(runes)
}

延伸閱讀