轨道
/
Julia
Julia
/
练习
/
日志级别
日志级别

日志级别

学习练习

简介

Julia 中的 String 是字符的有序序列。正如手册所指出的:“当然,真正的麻烦在于有人追问字符究竟是什么。”

这些复杂之处大多会在 Chars 概念中更详细地讲到,这里先简要说明:

  • Julia 中的所有字符串都是 Unicode。
  • 如果字符串恰好也是 ASCII(大小写英文字母 A 到 Z、数字和少量标点符号),处理起来就_很轻松_。
  • 世界上大多数其他书写系统也_可以_处理,但需要更小心。

字符串字面量

一般来说,字符串用双引号 " " 括起来。 单引号 ' ' 只用于 Chars。

也可以使用三引号 """ """,这样字符串内部可以直接使用 " 而无需转义。 更重要的是,多行字符串中多余的缩进会被去掉。

julia> """
       Multiline
       
       """
"Multiline\nString\n"

转义字符

和从 C 开始的大多数语言一样,Julia 用反斜杠 \ 进行转义:

  1. 输入非打印字符,比如上一个例子中的换行符 \n。
  2. 保护那些本来具有特殊含义的字符,比如 \$(见下文)。

索引

原则上,字符串可以像数组一样索引:

julia> s[1], s[5]
('J', 'a')

对 “Julia” 这样的 ASCII 字符串来说,这显然没问题,但这样会忽略世界上大多数语言。

以《贝奥武夫》中的一个字符为例,这部作品用古英语写成,距今约 1500 年。

“Hrōðgār” 显然是 7 个字符,但它们并不都是 ASCII。

julia> king = "Hrōðgār"
"Hrōðgār"

julia> length(king)
7

julia> isascii(king)
false

# collect() converts to a Char vector:
julia> collect(king)
7-element Vector{Char}:
 'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
 'ō': Unicode U+014D (category Ll: Letter, lowercase)
 'ð': Unicode U+00F0 (category Ll: Letter, lowercase)
 'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
 'ā': Unicode U+0101 (category Ll: Letter, lowercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)

这样索引一个字符串,开头还挺顺利,但到某个地方就会出问题:

julia> king[3]
'ō': Unicode U+014D (category Ll: Letter, lowercase)

julia> king[4]
ERROR: StringIndexError: invalid index [4], valid nearby indices [3]=>'ō', [5]=>'ð'

问题在于,这样索引数的是_字节_,而对于非 ASCII 字符串来说,字节和字符并不是一一对应的。 一个 Unicode/UTF-8 字符最多可能需要 4 个字节来表示,Chars 概念会进一步探讨这一点。

构建字符串

拼接

很多语言用 + 作为字符串拼接运算符,但 Julia 不是这样。 至少它的错误信息还挺有用。

julia> "s1 " + "s2"
ERROR: MethodError: no method matching +(::String, ::String)
The function `+` exists, but no method is defined for this combination of argument types.
String concatenation is performed with * 

julia> "s1 " * "s2"
"s1 s2"

string() 函数可以拼接任意数量的字符串。

julia> string("lots ", "of ", "bits")
"lots of bits"

join() 函数也能做到这一点,而且还能接收一个字符串数组,用指定的分隔符把它们拼接起来(默认为 "")。

julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"

注意,字符串是不可变的,拼接过程会发生复制。 因此,在循环中反复拼接效率很低,更好的做法是把片段收集到一个数组里,最后再一次性拼接起来。

重复

很简单:

julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"

插值

很多语言都有把计算得到的值插入字符串的办法,不过对于什么语法最好,各家分歧极大。

在 Julia 中,插值前面要加 $。

julia> myvar = 42
42

julia> "myvar evaluates to $myvar"
"myvar evaluates to 42"

julia> r = 5.3
5.3

julia> "A circle of radius $r has area $(π * r^2)"
"A circle of radius 5.3 has area 88.24733763933729"

如果省略括号会产生歧义,就必须加上括号;而单个标识符后面跟空白时,括号可以省略。

要在字符串里包含 $,就把它转义为 \$。

注意这个限制:目前的插值不支持格式化,比如指定显示几位小数。

@sprintf()

为了更精细地控制字符串的组装,Julia 从 C(以及之后的好几种语言:维基百科列出了大约 30 种)借鉴了 sprintf 函数。

在 Julia 中,它以 macro 的形式实现在 Printf 模块里,因此带有 @ 前缀。

julia> using Printf

julia> r = 5.3
5.3

julia> @sprintf("A circle of radius %.1f has area %.2f", r, π * r^2)
"A circle of radius 5.3 has area 88.25"

字符串函数

为了简单起见,本节只介绍那些返回新字符串、不修改输入的函数。 很多函数都有一个加上 ! 后缀的对应版本,会就地修改输入。

长度

一个字符串有多长? 有时候,这取决于你怎么数。

最简单的情况是 ASCII 字符串,每个字符占 1 个字节。

julia> string_asc = "Julia"
"Julia"

julia> length(string_asc)
5

julia> sizeof(string_asc)
5

length() 函数返回_字符_数,sizeof() 函数返回_字节_数。

换成其他字符集时,这个区别就变得重要了:

julia> king = "Hrōðgār"
"Hrōðgār"

julia> length(king)
7

julia> sizeof(king)
10

拆分

编程中常见的操作是:把一长串字符串按照一个或多个字符组成的分隔符拆成若干段。

最通用的做法是用 split() 函数。 分隔符默认是(任意)空白,也可以另行指定。

julia> split("my little string")
3-element Vector{SubString{String}}:
 "my"
 "little"
 "string"

julia> split("My_snake_case_string", "_")
4-element Vector{SubString{String}}:
 "My"
 "snake"
 "case"
 "string"

这些例子把字符串拆成了更小的_字符串_。 而把字符串拆成_字符_是另一种操作。

查找与替换

字符串里是否包含某个子串? 奇怪的是,有两个函数可以做这个判断:occursin() 和 contains(),它们只在参数顺序上不同。

julia> needle = "Julia"
"Julia"

julia> haystack = "Python, Julia and R"
"Python, Julia and R"

julia> occursin(needle, haystack)
true

julia> contains(haystack, needle)
true

如果想更精确地判断位置:

julia> startswith(haystack, "Python")
true

julia> endswith(haystack, "Python")
false

要取得子串的起始和结束下标,我们有好几个函数:

  • findfirst()
  • findlast()
  • findprev()
  • findnext()
julia> findfirst(needle, haystack)
9:13

要替换子串,用 x => y 语法列出各项改动。

julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"

从 Julia 1.7 开始,replace 函数可以接受任意数量的改动,并保证在替换过程中每个字符最多只被修改一次。

x => y 这种语法叫作 pair,它是 Julia 中一种重要的类型,Dicts and Pairs 概念会讲到更多细节。

空白字符

我们经常需要去掉字符串开头、结尾或两端的空白。

做这件事的函数有 lstrip()(左)、rstrip()(右)和 strip()(两端)。

julia> strip("\n\t  useful_bit\n\n")
"useful_bit"

改变大小写

这些函数的名字基本上一看就懂:

julia> s = repeat("aBcD ", 5)
"aBcD aBcD aBcD aBcD aBcD "

julia> uppercase(s)
"ABCD ABCD ABCD ABCD ABCD "

julia> lowercase(s)
"abcd abcd abcd abcd abcd "

julia> titlecase(s)  # Initial letter of each word uppercase
"Abcd Abcd Abcd Abcd Abcd "

说明

在本练习中,你将处理日志行。

每行日志都是一个字符串,格式如下:"[<LEVEL>]: <MESSAGE>"。

日志级别共有三种:

  • INFO
  • WARNING
  • ERROR

你有三个任务,每个任务都会接受一行日志,并要求你对其做相应的处理。

1. 从日志行中获取消息

实现message函数,返回日志行中的消息:

julia> message("[ERROR]: Invalid operation")
"Invalid operation"

开头和结尾的所有空白字符都应被移除:

julia> message("[WARNING]:  Disk almost full\r\n")
"Disk almost full"

2. 从日志行中获取日志级别

实现log_level函数,返回日志行的日志级别,且应以小写形式返回:

julia> log_level("[ERROR]: Invalid operation")
"error"

3. 重新格式化日志行

实现reformat函数,重新格式化日志行:把消息放在最前面,日志级别紧随其后并放在括号中:

julia> reformat("[INFO]: Operation completed")
"Operation completed (info)"

注意: 本练习中的所有字符串都是英文,且仅限于 ASCII 字符集。后续的概念将让你有机会处理 Unicode 字符。

通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Julia Exercism

准备好开始 日志级别 了吗?

注册 Exercism,借助 35 个概念128 个练习 和真人导师指导,学习并掌握 Julia,全部免费。