學習軌道
/
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 字串來說,這顯然行得通,但這忽略了世界上大多數的語言。

想想 Beowulf 裡的一個字元,這部作品是大約 15 個世紀前用古英語寫成的。

"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 字串來說,位元組和字元之間並不是 1:1 的關係。 一個 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 借用了sprintf函式(後來也有好幾種語言跟進,維基百科上列了大約 30 種)。

在 Julia 中,它是以Printf模組中的macro實作的,因此帶有@前置符號。

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