[1] "\"" "'" "\\"
str_view(x) # 输出的结果为转义后的结果[1] │ "
[2] │ '
[3] │ \
本章中,我们主要使用string包来实现字符串的高效处理。
这里我们主要介绍转义字符.
有时在创建字符串时,我们需要输入引号,这时我们需要使用转义字符\来转义它,即告诉计算机这个是字符串中的引号,而不是程序中的引号.
\时,同样需要转义符号\创建一个包含多个引号或反斜杠的字符串会令人感到十分困惑.
[1] │ double_quote <- "\"" # or '"'
│ single_quote <- '\'' # or "'"
如上代码中有非常多的反斜杠,要消除其中的过多的转义字符(主要是转义字符的转义字符),可以使用原始字符串.
原始字符串通常的形式为r"(strings)". 如果字符串中已经包含了)",则可以使用r[]或r{},如果还不够,还可以插入任意数量的破折号,使开头和结尾的字符串具有唯一性.例如r"--()--"或r"---()---.那么以上tricky字符串可以怎么改写呢?
tricky_amendment <- r"(double_quote <- "\"" # or'"'
single_quote <- '\'' # or "'")"
str_view(tricky_amendment)[1] │ double_quote <- "\"" # or'"'
│ single_quote <- '\'' # or "'"
最常见的特殊字符有:
\n: 新行\t: 制表符\u& \U: Unicode转义字符的字符串(非英语字符)更多的特殊字符可以通过?Quotes查看.
[1] │ one
│ tow
[2] │ one{\t}two
[3] │ µ
[4] │ 😄
创建包含以下值的字符串:
本部分我们将讨论: - str_c(),str_glue(),str_flatten()三个函数的用法. - 以上函数与mutate(),summarise()等函数连用的方法.
手动创建字符串只是最基础的内容,更多时候我们希望将一些文本与已经保存在数据中的文本连接起来,例如,将”hello”与一个保存有姓名的数据连接起来,创建一些列问候语.
str_c()函数mutate()连用# A tibble: 4 × 4
name greeting1 greeting2 greeting3
<chr> <chr> <chr> <chr>
1 Flora Hi Flora! Hi Flora! Hi Flora!
2 David Hi David! Hi David! Hi David!
3 Terra Hi Terra! Hi Terra! Hi Terra!
4 <NA> <NA> Hi you! Hi!
str_glue()函数使用str_c()将字符串结合会存在一个问题,我们会输入大量的引号",在字符串较长时我们很难一眼看出代码的总体目标.str_glue()函数提供了另外一种结合的方式.
# A tibble: 4 × 3
name greeting1 greeting2
<chr> <glue> <glue>
1 Flora Hi Flora! {Hi Flora!}
2 David Hi David! {Hi David!}
3 Terra Hi Terra! {Hi Terra!}
4 <NA> Hi NA! {Hi NA!}
str_flatten()函数str_c()和str_glue()可以很好地与mutate()配合使用,,因为它们的输出与输入长度相同。如果您想要一个能与summarize() 完美配合的函数,,即总是返回单个字符串的函数,,该怎么办呢?这就是str_flatten()的工作:它接收字符向量,并将向量中的每个元素合并为一个字符串:
str_flatten(c("x", "y", "z")) # 返回单个字符串[1] "xyz"
# 与summarise()函数连用
df <- tribble(
~name,
~fruit,
"Carmen",
"banana",
"Carmen",
"apple",
"Marvin",
"nectarine",
"Terence",
"cantaloupe",
"Terence",
"papaya",
"Terence",
"mandarin"
)
df |>
group_by(name) |>
summarise(
str_flatten(fruit, ", ")
)# A tibble: 3 × 2
name `str_flatten(fruit, ", ")`
<chr> <chr>
1 Carmen banana, apple
2 Marvin nectarine
3 Terence cantaloupe, papaya, mandarin
将下列表达式从str_c()转换为str_glue(),反之亦然.
str_c(“The price of”, food, ” is “, price)
str_glue(“I’m {age} years old and live in {country}”)
str_c(“\section{”, title, “}”)
The price of apple is 10
The price of rice is 20
str_c("I'm ", age, "years old and live in ", country)[1] "I'm 35years old and live in China" "I'm 45years old and live in US"
str_glue("\\\\section{{{title}}}")\\section{doctor}
\\section{teacher}
在处理字符串问题时,最好先将字符串的各部分分解出来再进行分析.如语句str_c("\\section{", title, "}")中的字符串,包括了三个部分:
\(N = 1\)
title
本部分我们将讨论: 如何提取一个字符串中的多个变量,主要有四个形式类似的函数:
separate_longer_delim(col, delim)、separate_longer_position(col, width)、separate_wider_delim(col, delim, names)、separate_wider_position(col, widths)。
pivot_longer()和pivot_wider()函数类似,,对新生成的数据进行长宽的变换。delim使用分隔符分割字符串,,position按指定宽度分割字符串。separate_wider_regex()函数,,可以根据正则表达式分割字符串,,在使用这个函数之前,,需要对正则表达式有所了解。df1 <- tibble(x = c("a,b,c", "d,e", "f"))
df1 |>
separate_longer_delim(x, delim = ",")# A tibble: 6 × 1
x
<chr>
1 a
2 b
3 c
4 d
5 e
6 f
df3 <- tibble(x = c("a10.1.2022", "b10.2.2011", "e15.1.2015"))
df3 |>
separate_wider_delim(x, delim = ".", names = c("code", "edition", "year"))# A tibble: 3 × 3
code edition year
<chr> <chr> <chr>
1 a10 1 2022
2 b10 2 2011
3 e15 1 2015
如果想在分割的过程汇中删除某列,,可以再names参数中将对应的列名设置为NA
df3 |>
separate_wider_delim(
x,
delim = ".",
names = c("code", NA, "edition")
)# A tibble: 3 × 2
code edition
<chr> <chr>
1 a10 2022
2 b10 2011
3 e15 2015
df4 <- tibble(x = c("202215TX", "202122LA", "202325CA"))
df4 |>
separate_wider_position(
x,
widths = c(year = 4, age = 2, state = 2)
)# A tibble: 3 × 3
year age state
<chr> <chr> <chr>
1 2022 15 TX
2 2021 22 LA
3 2023 25 CA
我们先看一个例子
df <- tibble(x = c("1-1-1", "1-1-2", "1-3", "1-3-2", "1"))
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z")
)Error in `separate_wider_delim()`:
! Expected 3 pieces in each element of `x`.
! 2 values were too short.
ℹ Use `too_few = "debug"` to diagnose the problem.
ℹ Use `too_few = "align_start"/"align_end"` to silence this message.
报错了!报错的原因在于,separate_wider_delim()函数在分列时,,需要每个字符串的分裂数量一致,,显然在上面代码中,,“1-3”和”1”的分裂数不同。在实际工作中,,这个分裂数的不同可能是多或少,,需要在separate_wider_delim()函数中加入too_few参数。
df |>
separate_wider_delim(
x,
delim = "-",
names = c("a", "x", "y", "z"),
too_few = "debug" # 使用调试模式输出结果
)# A tibble: 5 × 7
a x y z x_ok x_pieces x_remainder
<chr> <chr> <chr> <chr> <lgl> <int> <chr>
1 1 1-1-1 1 <NA> FALSE 3 ""
2 1 1-1-2 2 <NA> FALSE 3 ""
3 1 1-3 <NA> <NA> FALSE 2 ""
4 1 1-3-2 2 <NA> FALSE 3 ""
5 1 1 <NA> <NA> FALSE 1 ""
使用调试模式时,,输出中会多出三列:x_ok、x_pieces 和x_remainder(如果用不同的名称分隔变量,,会得到不同的前缀):
x_ok可以让你快速找到失败的输入.x_pieces列包含了各字符串分裂后的有多少个元素.x_remainder列包含了剩余的元素,通常在处理过多元素时使用.在得知问题后,,我们有两种处理的选择:
too_few参数,,用NA填补缺失的部分。df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_few = "align_start" # align_end和align_start控制填补NA的位置
)# A tibble: 5 × 3
x y z
<chr> <chr> <chr>
1 1 1 1
2 1 1 2
3 1 3 <NA>
4 1 3 2
5 1 <NA> <NA>
df <- tibble(x = c("1-1-1", "1-1-2", "1-3-5-6", "1-3-2", "1-3-5-7-9"))
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_many = "debug"
)# A tibble: 5 × 6
x y z x_ok x_pieces x_remainder
<chr> <chr> <chr> <lgl> <int> <chr>
1 1-1-1 1 1 TRUE 3 ""
2 1-1-2 1 2 TRUE 3 ""
3 1-3-5-6 3 5 FALSE 4 "-6"
4 1-3-2 3 2 TRUE 3 ""
5 1-3-5-7-9 3 5 FALSE 5 "-7-9"
我们同样有两种选择:
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_many = "drop"
)# A tibble: 5 × 3
x y z
<chr> <chr> <chr>
1 1 1 1
2 1 1 2
3 1 3 5
4 1 3 2
5 1 3 5
df |>
separate_wider_delim(
x,
delim = "-",
names = c("x", "y", "z"),
too_many = "merge"
)# A tibble: 5 × 3
x y z
<chr> <chr> <chr>
1 1 1 1
2 1 1 2
3 1 3 5-6
4 1 3 2
5 1 3 5-7-9
如何查询字符串长度,提取子字符串以及在图形和表中处理字符串.
str_length(c("a", "R for data science", NA))[1] 1 18 NA
str_length()函数可以和dplyr系列函数连用.
babynames |>
count(length = str_length(name), wt = n)# A tibble: 14 × 2
length n
<int> <int>
1 2 338150
2 3 8589596
3 4 48506739
4 5 87011607
5 6 90749404
6 7 72120767
7 8 25404066
8 9 11926551
9 10 1306159
10 11 2135827
11 12 16295
12 13 10845
13 14 3681
14 15 830
babynames |>
filter(str_length(name) == 15) |>
count(name, wt = n, sort = TRUE)# A tibble: 34 × 2
name n
<chr> <int>
1 Franciscojavier 123
2 Christopherjohn 118
3 Johnchristopher 118
4 Christopherjame 108
5 Christophermich 52
6 Ryanchristopher 45
7 Mariadelosangel 28
8 Jonathanmichael 25
9 Christianjoseph 22
10 Christopherjose 22
# ℹ 24 more rows
str_sub(字符串, start, end)
str_sub()函数同样可以和dplyr系列函数连用.
# A tibble: 1,924,665 × 7
year sex name n prop first last
<dbl> <chr> <chr> <int> <dbl> <chr> <chr>
1 1880 F Mary 7065 0.0724 M y
2 1880 F Anna 2604 0.0267 A a
3 1880 F Emma 2003 0.0205 E a
4 1880 F Elizabeth 1939 0.0199 E h
5 1880 F Minnie 1746 0.0179 M e
6 1880 F Margaret 1578 0.0162 M t
7 1880 F Ida 1472 0.0151 I a
8 1880 F Alice 1414 0.0145 A e
9 1880 F Bertha 1320 0.0135 B a
10 1880 F Sarah 1288 0.0132 S h
# ℹ 1,924,655 more rows
提取每个婴儿姓名中中间的字母.
babynames |>
mutate(
middle_letter = if_else(
str_length(name) %% 2 == 1,
str_sub(name, (str_length(name) / 2 + 1), (str_length(name) / 2 + 1)),
str_sub(name, (str_length(name) / 2 - 1), (str_length(name) / 2))
)
)# A tibble: 1,924,665 × 6
year sex name n prop middle_letter
<dbl> <chr> <chr> <int> <dbl> <chr>
1 1880 F Mary 7065 0.0724 Ma
2 1880 F Anna 2604 0.0267 An
3 1880 F Emma 2003 0.0205 Em
4 1880 F Elizabeth 1939 0.0199 a
5 1880 F Minnie 1746 0.0179 in
6 1880 F Margaret 1578 0.0162 rg
7 1880 F Ida 1472 0.0151 d
8 1880 F Alice 1414 0.0145 i
9 1880 F Bertha 1320 0.0135 er
10 1880 F Sarah 1288 0.0132 r
# ℹ 1,924,655 more rows
处理非英语文本难免遇到意料之外的难题,包括字符编码问题、带变音符的字母、地区敏感的字符串排序与大小写转换。
guess_encoding()函数猜测字符串的编码。在处理带重音符号的语言时,确定字母位置(例如使用 str_length() 和 str_sub() )会面临重大挑战,因为带重音符号的字母可能被编码为单个字符(如ü),也可能通过组合无重音字母(如 u)和变音符号(如¨)形成两个字符。例如,以下代码展示了两种看起来完全相同的ü表示方式:
u <- c("\u00fc", "u\u0308")
str_length(u) # 字符串u中的两个字符输出相同[1] 1 2
str_sub(u, 1, 1) # 但提取相同位置的字符不同[1] "ü" "u"
u[[1]] == u[[2]] # 两者也不完全相同[1] FALSE
str_equal(u[[1]], u[[2]]) # 识别两个字符串的输出外观是否相同[1] TRUE
stringi::stri_locale_list()函数来查看stringr支持哪些区域设置dplyr::arrange() 对字符串进行排序时也会出现这种情况,因此它还有一个 locale 参数。正则表达式是一种简洁而强大的语言,用于描述字符串中的模式。本节我们将通过几个简单的示例了解正则表达式的基本概念。
str_view()( 小节 10.1.2 有介绍)可以显示字符串向量所匹配的元素,并用<>包围,并用蓝色高亮显示匹配的元素,这个函数是学习正则表达式的利器。
., +, *, [,], ?,都有特殊的含义,被称为元字符:
.:匹配任意单字符。?:匹配指定字符0次或1次。+:匹配指定字符至少1次。*:匹配指定字符任意次数(包括0次)。[]:匹配一组字符其中的字符,[^]反匹配。|:表示多个匹配模式共同作用。# 匹配文字字符
str_view(fruit, "berry") [6] │ bil<berry>
[7] │ black<berry>
[10] │ blue<berry>
[11] │ boysen<berry>
[19] │ cloud<berry>
[21] │ cran<berry>
[29] │ elder<berry>
[32] │ goji <berry>
[33] │ goose<berry>
[38] │ huckle<berry>
[50] │ mul<berry>
[70] │ rasp<berry>
[73] │ salal <berry>
[76] │ straw<berry>
[2] │ <ab>
[3] │ <ae>
[6] │ e<ab>c
# 元字符匹配-找出所有包含字母a+三个字母+字母e的水果字符串
str_view(fruit, "a...e") [1] │ <apple>
[7] │ bl<ackbe>rry
[48] │ mand<arine>
[51] │ nect<arine>
[62] │ pine<apple>
[64] │ pomegr<anate>
[70] │ r<aspbe>rry
[73] │ sal<al be>rry
[1] │ <a>
[2] │ <ab>
[3] │ <ab>bc
[2] │ <ab>
[3] │ <abb>c
[1] │ <a>
[2] │ <ab>
[3] │ <abb>c
# 元字符匹配-查找包含由元音包围的 "x "或由辅音包围的 "y "的单词
str_view(words, "[aeiou]x[aeiou]")[284] │ <exa>ct
[285] │ <exa>mple
[288] │ <exe>rcise
[289] │ <exi>st
str_view(words, "[^aeiou]y[^aeiou]")[836] │ <sys>tem
[901] │ <typ>e
# 匹配包含apple,melon,nut的字符串
str_view(fruit, "apple|melon|nut") [1] │ <apple>
[13] │ canary <melon>
[20] │ coco<nut>
[52] │ <nut>
[62] │ pine<apple>
[72] │ rock <melon>
[80] │ water<melon>
正则表达式非常紧凑,而且使用了大量标点符号,因此一开始可能会让人觉得难以理解和阅读。别担心,只要多加练习,就可以很好的掌握.
我们可以通过stringr包中的一些拥有重要功能的有用的函数来加速理解正则表达式的过程.
str_detect()返回一个逻辑值向量,这个逻辑值向量与初始向量长度相同,可以与filter()和summarise()函数完美搭配。
str_subset()函数返回一个只包含匹配字符串的字符向量。str_which()函数返回一个给出匹配字符串位置的整数向量。str_detect(c("a", "b", "c"), "[aeiou]")[1] TRUE FALSE FALSE
str_subset(c("a", "b", "c"), "[aeiou]")[1] "a"
[1] 1
# 与mutate()连用
babynames |>
filter(str_detect(name, "x")) |> # 筛选所有包含x的行
count(name, wt = n, sort = TRUE) # 以n为基础,计算总数# A tibble: 974 × 2
name n
<chr> <int>
1 Alexander 665492
2 Alexis 399551
3 Alex 278705
4 Alexandra 232223
5 Max 148787
6 Alexa 123032
7 Maxine 112261
8 Alexandria 97679
9 Maxwell 90486
10 Jaxon 71234
# ℹ 964 more rows

str_count()返回每个字符串中匹配模式出现的次数。"[aeiouAEIOU]"表示同时匹配大写小写元音字母。regex("[aeiou], ignore_case = TRUE")。str_to_lower()/str_to_upper()。[1] 2 0 1
# A tibble: 97,310 × 5
name n vowels vowels_1 consonants
<chr> <int> <int> <int> <int>
1 Aaban 10 3 3 2
2 Aabha 5 3 3 2
3 Aabid 2 3 3 2
4 Aabir 1 3 3 2
5 Aabriella 5 5 5 4
6 Aada 1 3 3 1
7 Aadam 26 3 3 2
8 Aadan 11 3 3 2
9 Aadarsh 17 3 3 4
10 Aaden 18 3 3 2
# ℹ 97,300 more rows
str_replace() 和 str_replace_all() 替换匹配的文本。str_remove() / str_remove_all()删除匹配的字符。mutate()中进行数据清洗,通常需要多次重复处理以应对格式不一致的情况。# 替换
x <- c("apple", "pear", "banana")
str_replace(x, "[aeiou]", "-") # 只替换第一个匹配[1] "-pple" "p-ar" "b-nana"
str_replace_all(x, "[aeiou]", "-") # 替换所有匹配[1] "-ppl-" "p--r" "b-n-n-"
# 删除
str_remove(x, "[aeiou]") # 只删除第一个匹配[1] "pple" "par" "bnana"
str_remove_all(x, "[aeiou]") # 删除所有匹配[1] "ppl" "pr" "bnn"
使用 separate_wider_regex() 可将结构化的字符串拆成多列。
too_few = "debug" 可以定位匹配失败的原因(类似@sec-wider-problems)。df <- tribble(
~str ,
"<Sheryl>-F_34" ,
"<Kisha>-F_45" ,
"<Brandon>-N_33" ,
"<Sharon>-F_38" ,
"<Penny>-F_58" ,
"<Justin>-M_41" ,
"<Patricia>-F_84"
)
# 使用separate_wider_regex()函数df中的人民、性别和年龄
df |>
separate_wider_regex(
str,
patterns = c(
"<",
name = "[A-Za-z]+",
">-",
gender = ".",
"_",
age = "[0-9]+"
)
)# A tibble: 7 × 3
name gender age
<chr> <chr> <chr>
1 Sheryl F 34
2 Kisha F 45
3 Brandon N 33
4 Sharon F 38
5 Penny F 58
6 Justin M 41
7 Patricia F 84
像字符串一样,正则表达式使用反斜杠 \ 进行转义。所以,为了匹配字面上的 .,需要使用正则表达式 \.。问题在于,我们是用字符串来表示正则表达式的,而字符串中 \ 同样是转义符。因此,要表示正则表达式 \.,需要写成字符串 "\\.",也就是“嵌套式双重转义”。
r"()" 来表示正则表达式,在这种情况下,反斜杠不需要转义。.、$、|、*、+、 ?、 {、}、(、 ) 等特殊字符,也可使用字符类 [.]、[$] 等方式表示其字面值。默认情况下,正则表达式会匹配字符串中的任意部分,但有时我们希望匹配字符串的开头或结尾。^ 用于匹配字符串的开头,$ 用于匹配字符串的结尾。
^ 与 $ 即可。\b 可用于匹配单词边界(即单词的开头或结尾),常用于避免误匹配。# 匹配开头
str_view(fruit, "^a") # 匹配以字母a开头的字符串[1] │ <a>pple
[2] │ <a>pricot
[3] │ <a>vocado
# 匹配结尾
str_view(fruit, "a$") # 匹配以字母e结尾的字符串 [4] │ banan<a>
[15] │ cherimoy<a>
[30] │ feijo<a>
[36] │ guav<a>
[56] │ papay<a>
[74] │ satsum<a>
# 完全匹配
str_view(fruit, "apple") # 匹配包含apple字符串 [1] │ <apple>
[62] │ pine<apple>
str_view(fruit, "^apple$") # 完全匹配apple字符串[1] │ <apple>
[4] │ <sum>(x)
字符类用于匹配集合中的任一字符。可用 [] 来构造,例如 [abc] 匹配 a、b 或 c,[^abc] 匹配除这三者外的任意字符。在 [] 内部,除 ^ 外,还有两个具有特殊含义的字符:
- 表示范围,如 [a-z] 表示小写字母,[0-9] 表示数字;\ 用于转义特殊字符,如 [\^\-\]] 匹配 ^、-、]。此外,一些常见的字符类还有简写形式:
\d:数字,\D:非数字;\s:空白字符,\S:非空白;\w:字母或数字,\W:非字母或数字。x <- "abcd ABCD 12345 -!@#%."
str_view(x, "[abc]+") # 匹配abc[1] │ <abc>d ABCD 12345 -!@#%.
str_view(x, "[a-z]+") # 匹配小写字母[1] │ <abcd> ABCD 12345 -!@#%.
str_view(x, "[^a-z0-9]+") # 匹配除小写字母和数字[1] │ abcd< ABCD >12345< -!@#%.>
# 注意-字符同样需要转义
str_view("a-b-c", "[a-c]") # 匹配a到c的小写字母[1] │ <a>-<b>-<c>
str_view("a-b-c", "[a\\-c]") # 匹配a,-,c三个字符[1] │ <a><->b<-><c>
量词用于指定前一个字符或字符类的重复次数。常用的量词有:
?:匹配0次或1次;+:匹配1次或多次;*:匹配0次或多次;{n}:匹配恰好 n 次;{n,}:匹配至少 n 次;{n,m}:匹配至少 n 次,但不超过 m 次。使用圆括号 () 可将多个字符组合成一个整体,称为分组。分组后的整体可与量词结合使用,也可通过反向引用在替换操作中重复使用。
括号不仅可以控制优先级,还能创建“捕获组”(capturing group),形如(...),以便在后续使用匹配子模式的结果。具体来看:\1 表示与第一个括号的内容相同,\2 表示第二个,以此类推。
有一些设置可以用来调整正则表达式的细节,这在其他语言中通常被称为 flags(标志)。可以通过 regex() 函数将字符串包裹起来来使用这些设置。常用的标志有:
ignore_case = TRUE:匹配时忽略大小写。dotall = TRUE:让.匹配一切字符,包括\n。multiline = TRUE:让 ^ 和 $ 匹配每一行的开头和结尾。comments = TRUE:允许使用空格和注释来增强可读性。可以在正则表达式中添加空格和 # 注释来解释每一部分。这些空格默认会被忽略,如要匹配空格或 #,则需使用反斜杠转义。[1] │ <banana>
[1] │ <banana>
[2] │ <Banana>
[3] │ <BANANA>
[1] │ Line 1<
│ Line> 2<
│ Line> 3
# 让 ^ 和 $ 匹配每一行的开头和结尾
str_view(x, "^Line") # 只匹配第一行[1] │ <Line> 1
│ Line 2
│ Line 3
[1] │ <Line> 1
│ <Line> 2
│ <Line> 3
# 正则中加入注释
phone <- regex(
r"(
\(? # 可选的左括号
(\d{3}) # 三位区号
[)\-]? # 可选的右括号或短横线
\ ? # 可选空格
(\d{3}) # 三位号码
[\ -]? # 可选空格或短横线
(\d{4}) # 四位号码
)",
comments = TRUE
)
str_extract(c("514-791-8141", "(123) 456 7890", "123456"), phone)[1] "514-791-8141" "(123) 456 7890" NA
除了 regex() 函数包裹的正则表达式标志设置,还可以通过 fixed() 函数来关闭正则表达式的规则,直接按字面意义匹配。
fixed()中也可以配合 ignore_case = TRUE 忽略大小写:正则表达式是最紧凑的语言表达之一,一开始它们肯定会令人困惑,但当我们不断训练我们眼睛来阅读它们并训练我们的大脑来理解它们时,我们就会解锁一项可以在 R 和许多其他地方使用的强大技能。
https://www.regular-expressions.info/tutorial.html 包含了有关正则表达式的高级功能,我们可以使用它来了解正则表达式的最高级功能以及它们在幕后如何工作。
TODO: 深入学习正则表达式。