學習軌道
/
Go
Go
/
練習
/
日誌、日誌、日誌!
日誌、日誌、日誌!

日誌、日誌、日誌!

學習練習

簡介

Go 裡的rune型別是int32的別名。 由於底層是int32型別,rune型別儲存的是帶正負號的 32 位元整數值。 不過,rune型別和int32型別不同的地方在於,它儲存的整數值代表單一的 Unicode 字元。

Unicode 與 Unicode 碼點

Unicode 是 ASCII 的超集合,它為每個字元指定一個獨一無二的數字來表示該字元。 這個獨一無二的數字稱為 Unicode 碼點。 Unicode 的目標,是把世界上所有的字元,包括各種字母、數字、符號,甚至 emoji,都用 Unicode 碼點來表示。

在 Go 裡,rune型別代表單一的 Unicode 碼點。

下表列出幾個 Unicode 字元範例,以及它們的 Unicode 碼點和十進位值:

Unicode 字元 Unicode 碼點 十進位值
0 U+0030 48
A U+0041 65
a U+0061 97
¿ U+00BF 191
π U+03C0 960
🧠 U+1F9E0 129504

UTF-8

UTF-8 是一種可變寬度的字元編碼,用來把每個 Unicode 碼點編碼成 1、2、3 或 4 個位元組。 由於一個 Unicode 碼點最多可以編碼成 4 個位元組,rune型別必須能容納最多 4 個位元組的資料。 這就是rune型別是int32別名的原因,因為int32型別正好能容納最多 4 個位元組的資料。

Go 的原始碼檔案是以 UTF-8 編碼。

使用 rune

宣告rune型別的變數時,把一個字元放在單引號裡就行了:

myRune := '¿'

由於rune只是int32的別名,印出 rune 的型別會得到int32:

myRune := '¿'
fmt.Printf("myRune type: %T\n", myRune)
// Output: myRune type: int32

同樣地,印出 rune 的值會得到它的整數(十進位)值:

myRune := '¿'
fmt.Printf("myRune value: %v\n", myRune)
// Output: myRune value: 191

若要印出 rune 所代表的 Unicode 字元,請使用%c格式化動詞:

myRune := '¿'
fmt.Printf("myRune Unicode character: %c\n", myRune)
// Output: myRune Unicode character: ¿

若要印出 rune 所代表的 Unicode 碼點,請使用%U格式化動詞:

myRune := '¿'
fmt.Printf("myRune Unicode code point: %U\n", myRune)
// Output: myRune Unicode code point: U+00BF

rune 與字串

Go 的字串是以 UTF-8 編碼,這表示字串裡包含的是 Unicode 字元。 字串中的字元會依它所代表的 Unicode 字元,儲存並編碼成 1、2、3 或 4 個位元組。

在 Go 裡,序列是用切片來表示,而這些切片可以用 range 疊代。 當我們對字串做疊代時,Go 會把字串轉換成一連串的 rune,每個 rune 佔 4 個位元組(別忘了,rune 型別是int32的別名!)

雖然字串只是一段位元組切片,但range關鍵字疊代的是字串的 rune,而不是它的位元組。

在這個範例中,index變數代表目前 rune 位元組序列的起始索引,char變數則代表目前的 rune:

myString := "❗hello"
for index, char := range myString {
  fmt.Printf("Index: %d\tCharacter: %c\t\tCode Point: %U\n", index, char, char)
}
// Output:
// Index: 0	Character: ❗		Code Point: U+2757
// Index: 3	Character: h		Code Point: U+0068
// Index: 4	Character: e		Code Point: U+0065
// Index: 5	Character: l		Code Point: U+006C
// Index: 6	Character: l		Code Point: U+006C
// Index: 7	Character: o		Code Point: U+006F

由於 rune 可以佔 1、2、3 或 4 個位元組,字串的長度不一定等於字串中的字元數。 用內建的len函式取得字串以位元組計的長度,並用utf8.RuneCountInString函式取得字串中的 rune 數量:

import "unicode/utf8"

myString := "❗hello"
stringLength := len(myString)
numberOfRunes := utf8.RuneCountInString(myString)

fmt.Printf("myString - Length: %d - Runes: %d\n", stringLength, numberOfRunes)
// Output: myString - Length: 8 - Runes: 6

說明

你的任務是建立一個日誌函式庫,用來協助管理組織的日誌。 這個函式庫讓使用者能辨識某則日誌是由哪個應用程式發出、修復損毀的日誌,以及判斷某一行日誌是否在特定的字元數限制之內。

1. 辨識日誌是由哪個應用程式發出

日誌來自多個應用程式,每個應用程式都使用自己專屬的日誌格式。 日誌在存入日誌聚合系統之前,必須先辨識出發出它的應用程式。

實作Application函式,它接受一行日誌,並回傳發出這行日誌的應用程式。

若要辨識某一行日誌是由哪個應用程式發出,請依照下表搜尋這行日誌中的特定字元:

應用程式 字元 Unicode 碼點
recommendation ❗ U+2757
search 🔍 U+1F50D
weather ☀ U+2600

如果某一行日誌不包含上表中的任何字元,就對呼叫方回傳default。 如果某一行日誌包含上表中多個字元,就回傳從左到右在這行日誌中第一個找到的字元所對應的應用程式。

Application("❗ recommended search product 🔍")
// => recommendation

2. 修復損毀的日誌

由於日誌基礎架構中存在一個罕見但持續發生的 bug,日誌中的某些字元可能會損毀。 花了一些時間找出損毀的字元及其原始值之後,你決定更新日誌函式庫,來協助修復損毀的日誌。

實作Replace函式,它接受一行日誌、一個損毀的字元,以及原始值,並回傳一行修改後的日誌,其中所有出現的損毀字元都已取代為原始值。

log := "please replace '👎' with '👍'"

Replace(log, '👎', '👍')
// => please replace '👍' with '👍'"

3. 判斷日誌是否能顯示

負責顯示日誌的系統對於每行日誌可顯示的字元數設有限制。 因此,使用者希望這個函式庫包含一個輔助函式,用來判斷某一行日誌是否在特定的字元數限制之內。

實作WithinLimit函式,它接受一行日誌與字元數限制,並回傳這行日誌是否在字元數限制之內。

WithinLimit("hello❗", 6)
// => true
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Go Exercism

準備好開始 日誌、日誌、日誌! 了嗎?

註冊 Exercism,透過 34 個概念165 個練習 和真人引導來學習並精通 Go,全部免費。