轨道
/
Go
Go
/
练习
/
日志,日志,日志!
日志,日志,日志!

日志,日志,日志!

学习练习

简介

Go 中的rune类型是int32的别名。 rune类型底层是int32,因此它存储的是一个有符号的 32 位整数值。 不过,与int32类型不同,rune类型中存储的整数值表示单个 Unicode 字符。

Unicode 与 Unicode 码点

Unicode 是 ASCII 的超集,它为每个字符分配一个唯一的编号,以此来表示字符。 这个唯一的编号称为 Unicode 码点。 Unicode 的目标是把世界上所有的字符都表示为 Unicode 码点,包括各种字母、数字、符号,甚至 emoji。

在 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,全部免费。